关于__autoreleasing,你真的懂了吗?

上一篇文章介绍了__unsafe_unretained

这里我们要借助__unsafe_unretained来理解__autoreleasing,童鞋们肯定会说__autoreleasing,这么简单,就是将指向的对象延迟销毁,这里我要将我研究__autoreleasing的过程中遇到的坑一一分析出来,如果你也遇到过,那我们可以一起讨论一下,或者你觉得我哪里分析得不对,请提出来,因为我也在学习中,如果你还没有遇到过我提出来的这些问题,你也可以看看。

这里插入一点小知识:

    id __strong obj0;
    id __weak obj1;
    id __autoreleasing obj2;
    
    // __strong,__weak,__autoreleasing可以保证附有这些修饰符的自动变量初始化为nil
    
    // 上面源码和下面效果等同
    
    id __strong obj0 = nil;
    id __weak obj1 = nil;
    id __autoreleasing obj2 = nil;
//在文件中引入以下声明即可使用这两个函数
    extern void _objc_autoreleasePoolPrint();//打印注册到自动释放池中的对象
    extern uintptr_t _objc_rootRetainCount(id obj);//获取对象的引用计数


在编译器选项“-S”的同时运行clang,可以取得程序汇编输出,如:

clang -S 文件名

     

     /****************以下如果未特别说明,都是在ARC环境下****************/

     /********************分割线************************/

    

前言:

这里先说几个坑

第一个坑,首先举两个例子:

//例一
    id __unsafe_unretained obj1 = nil;
    {
        id  obj0 = [NSMutableArray array];
        [obj0 addObject:@"obj"];
        obj1 = obj0;
        NSLog(@"obj0 = %@", obj0);
    }
    
    NSLog(@"obj1 = %@", obj1);
//例二:
    id __unsafe_unretained obj1 = nil;
    {
        id  obj0 = [[NSMutableArray alloc]init];
        [obj0 addObject:@"obj"];
        obj1 = obj0;
        NSLog(@"obj0 = %@", obj0);
    }
    
    NSLog(@"obj1 = %@", obj1);

看到这两个例子,编译执行,都会在最后一条语句崩溃掉,你肯定会问我,它们有啥区别啊,仅仅是obj0对象的创建方式不一样,是的,我待会就要分析它们崩溃的原因,分析中你就会发现,这两种创建方式在ARC下其实是有区别的,虽然都不用我们自己来管理内存的释放,但是,理解一下底层的ARC机制也是好的。

分析:

这两个例子中,obj0在中括号也就是作用域内,是强引用可变数组对象的,作用域外obj0强引用失效,并且没有其他指针强引用这个对象,所以自动释放持有的对象,obj1就相当于野指针,访问野指针就会崩溃,这些上一节已经分析过了,我们继续看下一个例子:

//例三:
    id __unsafe_unretained obj1 = nil;
    
    {
        id obj0 = [NSMutableArray arrayWithObjects:@"obj",nil];
        obj1 = obj0;
        NSLog(@"obj0 = %@", obj0);
    }
    
    NSLog(@"obj1 = %@", obj1);

编译执行,正常打印信息:

2016-11-13 16:14:58.373 DebugDemo[3605:119189] obj0 = (

obj

)

2016-11-13 16:14:58.374 DebugDemo[3605:119189] obj1 = (

obj

) 

怎么回事,为什么没有崩溃,说好的野指针呢?这会有木有觉得自己掉坑里了,反正我当时是一脸懵逼...

问题我们先留在这里,我们先来看看编译器ARC的实现,再来详细分析为什么


再来说说第二个坑

+ (id)Object
{
return [NSMutableArray array];
}
+ (id)allocObject
{
return [NSMutableArray array];
}

执行以下代码:

//例四
id __unsafe_unretained obj1 = nil;
{
id obj0 = [[self class] Object];
[obj0 addObject:@"obj"];
obj1 = obj0;
NSLog(@"obj0 = %@", obj0);
}
NSLog(@"obj1 = %@", obj1);

执行结果,正常打印,不会崩溃

那么我们将类方法修改成如下:

+ (id)Object
{
NSMutableArray *marr = [NSMutableArray array];
return marr;
}

再执行,崩溃,这里我们标记为例五

我们再调用以alloc开头的类方法:

//例六
id __unsafe_unretained obj1 = nil;
{
id obj0 = [[self class] allocObject];
[obj0 addObject:@"obj"];
obj1 = obj0;
NSLog(@"obj0 = %@", obj0);
}

NSLog(@"obj1 = %@", obj1);

还是崩溃


扩展:如果将两个类方法的实现换成如下,则会怎么样;

return [[NSMutableArray alloc]init];

答案是都会崩溃

     /********************分割线************************/

我们先来看看alloc的情况

{
     id __strong obj = [[NSObject alloc] init];
}

我们来看看它的模拟源代码:

    /* pseudo code by the compiler */
    id obj = objc_msgSend(NSObject, @selector(alloc));
    objc_msgSend(obj, @selector(init));
    objc_release(obj);

分析:变量作用域结束时,通过objc_release释放对象,虽然ARC有效时不能使用release方法,但由此可知编译器自动插入了release

我们再来看一下alloc/new/copy/mutableCopy以外的方法会是什么情况

{
        id __strong obj = [NSMutableArray array];
    }

模拟源代码:

 /* pseudo code by the compiler */
    id obj = objc_msgSend(NSMutableArray, @selector(array));
    objc_retainAutoreleasedReturnValue(obj);
    objc_release(obj);

分析:中间多了一个objc_retainAutoreleasedReturnValue函数的调用

这里可以查看ARC编译器文档

我们来看看objc_retainAutoreleasedReturnValue函数的解释:

id objc_retainAutoreleasedReturnValue(id value);
    
Precondition: value is null or a pointer to a valid object.
    
If value is null, this call has no effect. Otherwise, it attempts to accept a hand off of a retain count from a call to objc_autoreleaseReturnValue on value in a recently-called function or something it calls. If that fails, it performs a retain operation exactly like objc_retain.
    
Always returns value.

大概意思就是:如果value为空,这个函数无效,否则,它会试图从对valueobjc_autoreleaseReturnValue函数的调用中接受一个保留计数的移交,在最近被调用的函数中或者它调用的。如果失败,它执行一个和objc_retain完全一样的保留操作。

我们来看看objc_autoreleaseReturnValue函数的解释:

id objc_autoreleaseReturnValue(id value);
    
Precondition: value is null or a pointer to a valid object.
    
If value is null, this call has no effect. Otherwise, it makes a best effort to hand off ownership of a retain count on the object to a call to objc_retainAutoreleasedReturnValue for the same object in an enclosing call frame. If this is not possible, the object is autoreleased as above.
        
Always returns value.

大概意思就是:如果value为空,这个函数无效,否则,它会执行一个高效的保留计数持有者的移交,从对象上面,到封闭的调用帧里,调用objc_retainAutoreleasedReturnValue的相同对象,如果这是不可能的,那么这个对象会被自动释放就像objc_autorelease调用一样。

     

这里有点不好理解,继续往下看,下面会解释得很清楚。

这里我们解释一下:

     objc_retainAutoreleasedReturnValue函数主要用于最优化程序运行,顾名思义,它是用于自己持有(retain)对象的函数,但是它持有的对象应为返回注册在autoreleasepool中对象的方法,或是函数的返回值。像该源码这样,在调用alloc/new/copy/mutableCopy以外的方法,即NSMutableArray array类方法等调用之后,由编译器插入该函数。

     

     这种objc_retainAutoreleasedReturnValue函数是成对的,与之相对的函数是objc_autoreleaseReturnValue。它用于alloc/new/copy/mutableCopy方法以外的NSMutableArray array类方法等返回对象的实现上。下面我们看看NSMutableArray array类方法通过编译器会进行怎么样的转换;

+ (id) array {
        return [[NSMutableArray alloc] init];
    }

以下为该源码的转换,转换后的源代码使用了objc_autoreleaseReturnValue函数

/* pseudo code by the compiler */
    + (id) array
    {
        id obj = objc_msgSend(NSMutableArray, @selector(alloc));
        objc_msgSend(obj, @selector(init));
        return objc_autoreleaseReturnValue(obj);
    }

像该源代码这样,返回注册到autoreleasepool中对象的方法使用了objc_autoreleaseReturnValue函数返回注册到autoreleasepool中的对象,但是objc_autoreleaseReturnValue函数同objc_autorelease函数不同,一般不仅限于注册对象到autoreleasepool中。

     objc_autoreleaseReturnValue函数会检查使用该函数的方法或函数调用方的执行命令列表,如果方法或函数的调用方在调用了方法或函数后紧接着调用objc_retainAutoreleasedReturnValue函数,那么就不将返回的对象注册到autoreleasepool中,而是直接传递到方法或函数的调用方。objc_retainAutoreleasedReturnValue函数与objc_retain函数不同,它即便不注册到autoreleasepool中而返回对象,也能够正确地获取对象,通过objc_autoreleaseReturnValue函数和objc_retainAutoreleasedReturnValue函数的协作,可以不将对象注册到autoreleasepool中而直接传递,这一过程达到了最优化。

     /********************分割线************************/

    

上面的内容你都看懂了吗?看懂了我们就开始分析上面提出的疑问了

第一个坑分析:

例二崩溃的原因很简单,我们把模拟源代码写出来就知道了,我只贴重要的源码部分:

//...
    {
        id obj = objc_msgSend(NSObject, @selector(alloc));
        objc_msgSend(obj, @selector(init));
        //...
        objc_release(obj);
    }
    //...

例一崩溃的原因也很简单,分析如下:

OC源码:

+ (id) array {
        return [[NSMutableArray alloc] init];
    }

编译器源码:

+ (id) array
{
    id obj = objc_msgSend(NSMutableArray, @selector(alloc));
    objc_msgSend(obj, @selector(init));
        return objc_autoreleaseReturnValue(obj);
    }

这都是上面讲了的,我拷贝过来以便分析,那么例一的源码如下:

    //...
    {
        id obj = objc_msgSend(NSMutableArray, @selector(array));
        //...
        objc_retainAutoreleasedReturnValue(obj);
        objc_release(obj);
    }
    //...

根据上面最优化程序运行的分析,objc_autoreleaseReturnValue发现array类方法的调用方调用了objc_retainAutoreleasedReturnValue函数,那么就不会把返回的对象注册到autoreleasepool中了,因此我们可以再把上面的模拟编译器源码继续转换如下:

+ (id) array
    {
        id obj = objc_msgSend(NSMutableArray, @selector(alloc));
        objc_msgSend(obj, @selector(init));
        return obj;
    }
//...
    {
        //objc_retainAutoreleasedReturnValue函数与objc_retain函数不同,它即便不注册到autoreleasepool中而返回对象,也能够正确地获取对象
        id obj = objc_msgSend(NSMutableArray, @selector(array));
        //...
        objc_release(obj);
    }
    //...

看懂了吗?

我们继续分析例三:

我猜想是因为arrayWithObjects:类方法内部隐式调用了retain,或者没有实现编译器优化,我们运行以下代码看一下:

NSMutableArray *array1 = [NSMutableArray array];
    NSLog(@"retain count1 = %d", _objc_rootRetainCount(array1));
    
    NSMutableArray *array2 = [NSMutableArray arrayWithObjects:@"obj", nil];
    NSLog(@"retain count2 = %d", _objc_rootRetainCount(array2));

打印信息:

2016-11-16 12:01:28.268 WarningDemo[2027:155000] retain count1 = 1

2016-11-16 12:01:28.269 WarningDemo[2027:155000] retain count2 = 2

作用域结束以后,obj指针指向的内容并没有释放,而是在autoreleasepool中等待稍后释放。

那么我们验证一下,在外层加一个自动释放池,如下:

id __unsafe_unretained obj1 = nil;
    
    @autoreleasepool {
        id obj0 = [NSMutableArray arrayWithObjects:@"obj",nil];
        obj1 = obj0;
        NSLog(@"obj0 = %@", obj0);
    }
    
    NSLog(@"obj1 = %@", obj1);

结果如所料,完美崩溃。。。

第二个坑分析:

分析之前我们先介绍一个新的编译器内部函数,这个函数下面会用到

id objc_storeStrong(id *object, id value);

Precondition: object is a valid pointer to a __strong object which is adequately aligned for a pointer. value is null or a pointer to a valid object.

Performs the complete sequence for assigning to a __strong object of non-block type [*]. Equivalent to the following code:

id objc_storeStrong(id *object, id value) {

  value = [value retain];

  id oldValue = *object;

  *object = value;

  [oldValue release];

  return value;

}

Always returns value.

*

This does not imply that a __strong object of block type is an invalid argument to this function. Rather it implies that an objc_retain and not an objc_retainBlock operation will be emitted if the argument is a block.


我们还是直接上汇编源码吧,这样什么都清楚了,OC源码如下:

//main.m
#import <Foundation/Foundation.h>

@interface MyObject : NSObject
@end

@implementation MyObject

+ (id)Object
{
    return [NSMutableArray array];
}

+ (id)Objects
{
    id  obj = [NSMutableArray array];
    return obj;
}


+ (id)copyObject
{
    return [NSMutableArray array];
}
@end

int main() {
    //tag1
    {
        id  obj = [MyObject Object];
    }
    //tag2
    {
        id  obj = [MyObject Objects];
    }
    //tag3
    {
        id  obj = [MyObject copyObject];
    }

}


通过clang -S -fobjc-arc /Users/wanglijun/Desktop/main.m命令将源码转成汇编代码如下,我们把最关键的代码贴出来:

main实现


分析:

tag1汇编源码:

id obj = objc_msgSend(MyObject, @selector(Object));
objc_retainAutoreleasedReturnValue(obj);
objc_storeStrong(&obj,nil);

tag2汇编源码:

id obj = objc_msgSend(MyObject, @selector(Objects));
objc_retainAutoreleasedReturnValue(obj);
objc_storeStrong(&obj,nil);

tag3汇编源码:

id obj = objc_msgSend(MyObject, @selector(copyObject));
objc_storeStrong(&obj,nil);


Object实现:


分析:

Object汇编源码如下:

 + (id)Object
    {
        id obj = objc_msgSend(NSMutableArray, @selector(array));//由array的编译器源码可以知道,这个obj相当于[[[NSMutableArray alloc]init]autorelease];
        return obj;
    }   

因为这里直接返回并没有_objc_autoreleaseReturnValue方法的调用,因此后面main中的objc_retainAutoreleasedReturnValue方法就相当于objc_retain,此时可以打印obj的引用计数应该是2,后面objc_storeStrong方法相当于作用域结束执行release操作,因此作用域外,引用计数为1,以致于不会崩溃。


Objects实现:


分析:

Objects汇编源码如下:

+ (id) Objects
    {
        id obj = objc_msgSend(NSMutableArray, @selector(array));//由array的编译器源码和后面的objc_retainAutoreleasedReturnValue调用可以知道,这个obj相当于[[NSMutableArray alloc]init];
        objc_retainAutoreleasedReturnValue(obj);
        objc_retain(obj);
        objc_storeStrong(&obj,nil);//这里相当于执行release,和前面的objc_retain方法相消
        return _objc_autoreleaseReturnValue(obj);
    }    

里面两两相消最后得到的相当于

{
   id obj = objc_msgSend(NSMutableArray, @selector(alloc));
   objc_msgSend(NSMutableArray, @selector(init));
   return _objc_autoreleaseReturnValue(obj);
}

后面main中又紧接着调用了objc_retainAutoreleasedReturnValue,和上面的_objc_autoreleaseReturnValue相消,则main中相当于

id obj = objc_msgSend(NSMutableArray, @selector(alloc));

objc_msgSend(NSMutableArray, @selector(init));

objc_storeStrong(&objnil);

因此作用域外,对象已经销毁了。


copyObject实现:


分析:

copyObject汇编源码如下:

+ (id) copyObject
    {
        id obj = objc_msgSend(NSMutableArray, @selector(array));//由array的编译器源码和后面的objc_retainAutoreleasedReturnValue调用可以知道,这个obj相当于[[NSMutableArray alloc]init];
        objc_retainAutoreleasedReturnValue(obj);
        return obj;
    }

这个不分析了,太简单,可见alloc/new/copy/mutableCopy族类方法是不会使用编译器优化的。


最后扩展的内容自己分析,很简单。


     /********************分割线************************/

好,以上是前言,有木有被吓到,下面我们开始正文了,正式讲解__autoreleasing,别被吓到,上面你要是都懂了,下面都是小case,继续:

    /*ARC无效时*/
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    id obj = [[NSObject alloc] init];
    [obj autorelease];
    [pool drain];
    
    //等同于ARC下面的以下源码
    @autoreleasepool {
        id __autoreleasing obj = [[NSObject alloc] init];
    }
    
    //将上面的源码转换成编译器的模拟源代码如下:
    id pool = objc_autoreleasePoolPush();
    id obj = objc_msgSend(NSObject, @selector(alloc));
    objc_msgSend(obj, @selector(init)); objc_autorelease(obj);
    objc_autoreleasePoolPop(pool);
    
    //那么在使用alloc/new/copy/mutableCopy方法群之外的方法如何呢
    @autoreleasepool {
        id __autoreleasing obj = [NSMutableArray array];
    }
    //将上面的源码转换成编译器的模拟源代码如下:
    id pool = objc_autoreleasePoolPush();
    id obj = objc_msgSend(NSMutableArray, @selector(array));
    objc_retainAutoreleasedReturnValue(obj); objc_autorelease(obj);
    objc_autoreleasePoolPop(pool);

__autoreleasing:介于__strong__weak之间,使指向的对象延迟销毁,我们常可以在函数的输出参数和返回值上见到使用 ,

ARC有效时,用@autoreleasepool块替代NSAutoreleasePool类,用附有__autoreleasing修饰符的变量代替autorelease方法。

     

下面这些情况不用开发者显示的指定__autoreleasing

1.对象作为函数的返回值,编译器会自动将其注册到自动释放池。

//由于return使得对象变量超出其作用域,所以该强引用对应的自己持有的对象会被自动释放,但该对象作为函数的返回值,编译器会自动将其注册到autoreleasepool

2.id的指针或对象的指针在没有显示指定时会被附加上__autoreleasing修饰符。

     

情况一上面已经讲了,下面说一下情况二:

id objid __strong obj完全一样,那么id的指针id *obj又如何呢?可以由id __strong obj的例子类推出id __strong* obj吗?其实,退出来的是id__autoreleasing *obj。同样的,对象的指针NSObject **obj便成为了NSObject* __autoreleasing* obj

     

如下例子,在函数的输出参数上:

- (BOOL) performOperationWithError:(NSError **)error; 

同前面讲述的,id的指针或对象的指针会默认附加上__autoreleasing修饰符,因此上面的代码等同于以下源码:

- (BOOL) performOperationWithError:(NSError * __autoreleasing *)error; 

//这里要注意:函数中要判断error参数不是nil,因为空指针解引用会导致段错误”(segmentation fault)并使应用程序崩溃,调用者在不关心具体错误时,会给error参数传入nil,所以必须判断这种情况。

例如下面的源代码会产生编译器错误;

NSError *error = nil; 
NSError **pError = &error; 

修改如下正常:

NSError *error = nil;
NSError * __strong *pError = &error;

执行以下代码,这会编译器为什么没有报错

NSError __strong *error = nil;
    
BOOL result = [self performOperationWithError:&error];

是因为编译器自动的将该源代码转换成了下面的形式

NSError __strong *error = nil;
NSError __autoreleasing *tmp = error;
BOOL result = [obj performOperationWithError:&tmp];
error = tmp; 

当然也可以显式地指定方法参数中对象指针类型的所有权修饰符:

- (BOOL) performOperationWithError:(NSError * __strong *)error; 

This declaration shows how to pass an object without registering it to autoreleasepool. Although it is possible, you should not do that. From the caller’s point of view, to create an object with ownership, the method has to be in the alloc/new/copy/mutableCopy group. If it is not in the group, the caller should obtain an object without ownership. So, the argument should be qualified with __autoreleasing.

     

下面依然再举几个例子具体分析一下:

    id __unsafe_unretained obj1 = nil;
    
    @autoreleasepool {
        id __autoreleasing obj0 = [[NSObject alloc]init];
        obj1 = obj0;
        NSLog(@"obj0 = %@", obj0);
    }
    
    NSLog(@"obj1 = %@", obj1);

这里obj0无论使用__autoreleasing或者__strong都会崩溃,原因不一样,前者是因为被自动释放池释放,后者是因为超出作用域被编译器插入objc_release方法释放掉

上面讲到的例一和例二中,我们将obj0的创建代码添加修饰符__autoreleasing,变为

id __autoreleasing obj0 = [NSMutableArray array];

id __autoreleasing obj0 = [[NSMutableArray alloc]init];

则不会引发崩溃并正常打印

2016-11-11 13:55:52.083 WarningDemo[10107:1070158] obj0 = (

obj

)

2016-11-11 13:55:52.083 WarningDemo[10107:1070158] obj1 = (

obj

)

     

最后我们来看最后一个例子:

@autoreleasepool {
id __strong obj = [[NSObject alloc] init];
id __autoreleasing o = obj;
NSLog(@"retain count = %d", _objc_rootRetainCount(obj)); 
} 

运行结果如下:

retain count = 2

     

这个自行分析



猜你喜欢

转载自blog.csdn.net/junjun150013652/article/details/53149145