再谈ARC

1. ARC之前

最近买了本ios多线程和内存管理,对就是日本人写的那本书,然后看了目录就深深的鄙视了下,我都会嘛,然后看了下就傻逼了。感觉日本人对有的东西不是一般的严谨啊,非得要挖地三尺,这让我想到了当年金刚石大会的那个日本教授。
言归正传,看了这本书我才深深的感受到没有经历MRC时代的人是多么的幸福。ARC是多么的方便。
对于内存管理这部分,我想从头开始思考下,在N年前,在没有ARC(自动管理计数)的时候,我们是怎么写OC的呢?

    {
        id o1 = [[NSObject alloc] init];
        id o2 = [o1 retain];
        id o3 = [o1 retain];
        // use o1,o2,o3
        
        // free o1
        [o1 release];
        [o2 release];
        [o3 release];
       
       NSLog(@"%@",o3);
    }

由于oc采用的计数规则来控制内存的,这个应该大多数都明白。简单来讲,每一个对象的地址里面都存有一个retainCount的变量,retain操作使得retainCount+1 , release操作使得retainCount-1 , 当retainCount等于0的时候,就会自动调用delloc方法,这个变量所占用的内存就释放了。
通常来讲,我们将allloc,copy,new方法生成出来的对象会被自己持有。简单来讲,如果我们用alloc去创建一个对象然后赋值给一个变量,那么这个对象就归变量所持有,retainCount就变为1,如果别的变量想持有这个对象要怎么做呢?retain一下就好了。
so,按照原则,当我们释放对象的时候,如果我们retain了一次,那么就必须release一次。
如果release少了,那么内存得不到释放,也就是内存泄露了;如果release多了,那么编译器会报错:

objc[33744]: NSObject object 0x100600fd0 overreleased while already deallocating; break on objc_overrelease_during_dealloc_error to debug

真是太麻烦了,这样看来在上古时代的代码里面 retain和release的出现频率应该是最高的吧。
然后楼主发现,最后那个nslog居然能打出来对象,纳尼,最后楼主才反应过来,释放内存并不是把内存的东西置为空啊,而是让其标记为可用啊!基础学的不好真蛋疼~~~


        id o4 = [[NSObject alloc] init];
        [o4 release];
        
        id o5 = [[NSObject alloc] init];
        
        NSLog(@"%@",o4);
        NSLog(@"%@",o5);


   //2016-06-02 21:43:54.763 arc[33946:2099866] <NSObject: 0x1001024c0>
   //2016-06-02 21:43:54.763 arc[33946:2099866] <NSObject: 0x1001024c0>

看到了吧,内存的确是被释放了。来看下下一段代码


id getObj(){
    id a  = [[NSObject alloc] init];
    return a ;
}

int main(int argc, const char * argv[]) {
    
    {
        id o4 = getObj();
        NSLog(@"%lu",(unsigned long)[o4 retainCount]);
        [o4 release];

    }
}

纳尼,这是什么sb代码,没问题啊 retainCount是1。
但是按照我们的规则,o4去持有getOBj()返回的对象的时候,必须去retain一下,所以上面的代码应该改成
id o4 = [getObj() retain];
然后一看log出来的信息,SB了吧,是2!按照我们的理想不是应该是1么。回过头来我们看下getObj方法,a变量持有了对象,但是在作用域的最后并没有release,所以按照我们上面的分析,会造成内存泄露。但是最开始的错误代码阴差阳错的对了,可是这是我们不希望看到了。但是如果我们把a变量release掉,那么返回值就没有了啊~~~!@#@¥@#%¥……¥%……¥%@#¥@啊啊啊啊啊。现在至此,就可以引出下一个oc中重要的东西了autoreleasePool; 很多人都把这个东西和ARC联系在一起了,其实不然啊在MRC时代这个东西就已经很普及了。

2.自动释放池

在没有autorelease的时候,我们释放一个对象需要调用release,这个release是会立刻执行的,一个作用域的最后我们需要将所有的作用域变量都release一次,但是就像上面的例子,一样,如果是有返回值的作用域,怎么办?我们需要让它先作为返回值先返回,然后再某一个时间进行一次release,autorelease就可以做到。
我们改造一下我们的代码。

id getObj(){
    id a  = [[NSObject alloc] init];
    return [a autorelease];
}

int main(int argc, const char * argv[]) {
    
    id o2 = nil;
    
    @autoreleasepool {
       id o4 = [getObj() retain];
       o2 = [o4 retain];
       NSLog(@"retainCount : %lu",(unsigned long)[o2 retainCount]);
       [o4 release];
      //_objc_autoreleasePoolPrint();
    }
    
NSLog(@"retainCount : %lu",(unsigned long)[o2 retainCount]);
  
//2016-06-02 22:29:40.023 arc[34421:2135815] retainCount : 3
//2016-06-02 22:29:40.024 arc[34421:2135815] retainCount : 1  
    

看到了吗?当我们使用了autoreleasePool的时候,整个世界就光明了,完全按照我们最初的逻辑去走了,最后对象只有一个o2持有,retainCount=1。
autorelease方法会把对象注册到autoreleasePool里面,然后在autoreleasePool的最后调用release方法。所以这个问题解决了,我们把a变量release的时间推迟了,使得我们函数返回了所需要的对象,然而又没有破坏我们的规则。所以很多时候我们都不习惯直接release,而是autorelease让自动释放池去帮我们处理这些事情,带来了很多方便。

3. ARC的出现

接下来ARC出场了--自动管理计数,在采用ARC之后,你的代码里面就再也看不到retain release autorelease这样的方法了,因为编译器会自动帮我们在需要的地方添加这些方法,来轻松的管理我们的内存。
这里就不得不提到ARC引入的几个变量修饰符了

__Strong , __weak, __unsafe_unretained, __autoreleasing

1.__strong

先说一说Strong(强引用),这也是默认的修饰符,如果我们不给一个对象变量指定修饰符的话,那么默认是strong类型,strong类型意味着对象会被变量所持有,意味着引用计数会被加1。

{
id __strong a =[ [NSObject alloc] init]; //retainCount=1
id __strong b = a; //retainCount=2
}

编译器在__strong类型持有某个对象的时候自动的把这个对象retain一次(这里的retain是优化过的),而在代码块的最后,将自动release一次所有的strong变量。由于我们在第一节就说过alloc,new,copy,mutablecopy方法所出来的对象会被自己持有。但是不是通过上面方法返回的对象呢,比如在2中函数返回值所返回的对象?

{
  id getObj(){
    id a  = [[NSMutableArray alloc] init];
    return a;
}

int main(int argc, const char * argv[]) {

    @autoreleasepool {
        
         id __strong o4 = getObj();
        
        _objc_autoreleasePoolPrint();
        
    }     
/*objc[1722]: ##############
objc[1722]: AUTORELEASE POOLS for thread 0x10007f000
objc[1722]: 2 releases pending.
objc[1722]: [0x101800000]  ................  PAGE  (hot) (cold)
objc[1722]: [0x101800038]  ################  POOL 0x101800038
objc[1722]: [0x101800040]       0x1003001f0  __NSArrayM
objc[1722]: ##############

*/
}

通过这段代码我们可以看到ARC帮我们做了什么,在print的第一个自动释放池时候,0x1003001f0 __NSArrayM对象存在于池子中,这是因为由于给strong变量赋值是通过函数返回值的形式,为了保证strong变量可以接收到返回值,并且保证函数中alloc出来的对象还应该release一次,ARC将这个变量注册到了自动释放池里面去,那么在自动释放池的最后,这个变量会执行一次release操作,来确保不会发生内存泄露。
再看下一个嵌套的例子~


id getSubObj(){
    id a  = [[NSMutableArray alloc] initWithObjects:@"a", nil];
    _objc_autoreleasePoolPrint();
    return a;
}
id getObj(){
    id a = getSubObj();
    _objc_autoreleasePoolPrint();
    return a;
}

int main(int argc, const char * argv[]) {

    @autoreleasepool {  
        id __strong o4 = getObj();
        _objc_autoreleasePoolPrint();
    }

按照我们前面提到的逻辑,执行完 id __strong o4 = getObj();之后,obj应该在自动释放池里面注册了两次,然而实际上却只有一次,这就是ARC所做的工作,他会自动优化自动释放池,能够让对象不过度的注册到自动释放池里面,并且不会出现内存的问题(具体可以参考mac ox多线程管理的P67)。

2.__weak

如果一个变量使用了__weak修饰,那么这个变量就不持有这个对象。也就是说不会增加对象的引用计数,变量销毁的时候也不会调用对象的release方法。什么是不持有呢?就如同下面这段代码,weak变量既不能增加对象的引用计数,也不能销毁对象,当所指的对象被销毁之后,会被自动置为空!!

    @autoreleasepool {
        
        id __strong o4 = [[NSObject alloc] init];
        id __weak o3 =  o4;
        o3 = nil;
        NSLog(@"%@",o4); //<NSObject: 0x100103b30>
        
        o3 =  o4;
        o4 = nil;
        NSLog(@"%@",o3); //null
        
    }

那么weak是怎么实现的呢~其实程序中会有一张weak的表,一旦有变量被声明为weak,也就将这个变量注册到了这张表中,被废弃则从这张表中删除,所以如果一旦使用了大量的__weak,势必会对程序的资源有所消耗,正如书中所告诉我们的,只需要在避免循环引用的时候使用__weak,而避免滥用。

3.__autoreleasing

和strong一样我们一般不用为变量显式的添加autoreleasing修饰符。编译器会在合适的地方自动的添加这个符号,而这个修饰符的实际用途就是将对象在自动释放池里面去注册一次。
比如在使用对象的指针的时候,如果没有显式的制定修饰符,那么会默认加上__autoreleasing修饰符。比如在cocoa框架中,NSError这个类通常是用对象指针去传递的。而不是函数的返回值。

void sendError(id *a){
    
     *a = [[NSError alloc] initWithDomain:@"abc" code:1 userInfo:nil];
     _objc_autoreleasePoolPrint();
    
}

int main(int argc, const char * argv[]) {
    

    @autoreleasepool {
        

        NSError *error = [[NSError alloc] init];
        sendObj(&error);

objc[10274]: 2 releases pending.
objc[10274]: [0x101800000]  ................  PAGE  (hot) (cold)
objc[10274]: [0x101800038]  ################  POOL 0x101800038
objc[10274]: [0x101800040]       0x100603660  NSError

而在sendError函数中id的指针类型被自动声明成了autoreleasing,从而将error对象加入注册进了自动释放池,其实不难理解为什么要这么做,这样能够确保在自动释放池作用域内,对象不会被释放掉,引起程序的错误。

4. 总结

其实现在苹果一直在简化开发难度,ARC的引入正式证明了这么一点,一般来将,你不需要特别的深入了解内存管理机制,因为在大多数情况下ARC都能帮助我们正确的管理内存。只要我们按常规来写代码,程序应该不会出现很大的问题,像循环引用这种造成内存泄露的事情,使用weak修饰符就可以很好的解决了。另外就是内存释放造成空指针,只要搞清楚对象的作用域范围就不会出现这种问题了,比如在函数作用域中创建的对象,如果我们希望他的生命周期和父对象一样,那么一定要用父对象的Strong属性去持有她。如果想了解下更多底层的懂西,那么《Objective-C高级编程:iOS与OS+X多线程和内存管理 》这本是应该是不二的选择。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,980评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,422评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,130评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,553评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,408评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,326评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,720评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,373评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,678评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,722评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,486评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,335评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,738评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,283评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,692评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,893评论 2 335

推荐阅读更多精彩内容