//以下两种写法在ARC有效时是等价的
id obj = [[NSObject alloc] init];
id __strong obj = [[NSObject alloc] init];
那么在ARC有效时,__strong修饰符是怎么样的运行机制呢?
{
Person __strong *p1 = [[Person alloc] init];
//p1是个强指针,一般情况下都会省略__strong修饰符
//使用p1
//使用结束后不再需要且不允许调用[p1 release]方法
}
/*
在这里p1的生命周期结束,p1被废弃
Person对象已经没有强指针指向它了
编译器此时废弃内存中的Person对象
*/
Person.h
#import
#import "Dog.h"
@interface Person : NSObject{
//宠物,默认是__strong
Dog *_dog;
}
-(void)setDog:(Dog *)dog;
@end
Person.m
#import "Person.h"
@implementation Person
-(void)setDog:(Dog *)dog
{
_dog = dog;
}
@end
Dog.h
#import
#import "Person.h"
@interface Dog : NSObject{
//主人 默认是__strong
Person *_owner;
}
-(void)setOwner:(Person *)owner;
@end
Dog.m
#import "Dog.h"
@implementation Dog
-(void)setOwner:(Person *)owner
{
_owner = owner;
}
@end
int main(int argc, char *argv[])
{
Person *p1 = [[Person alloc] init];
Dog *d1 = [[Dog alloc] init];
[p1 setDog:d1];
[d1 setOwner:p1];
return 0;
}
/*
p1和d1指针失效
但是Person对象和Dog对象,因为形成了循环引用
互相有强指针指向对方
因为Person对象和Dog对象都不会被废弃
造成内存泄漏
*/
用一张图说明
Person __weak *p1 = [[Person alloc] init];
如果这样写,编译器是无法通过的。会报Assigning retained object to weak property object will be released after assignment的警告。
id __weak obj3 = [NSMutableArray array];
为什么呢?这里先不介绍,留在文章的第三部分,关于autorelease和返回值那一块介绍。
{
Person *p1 = [[Person alloc] init];
Person __weak *p2 = p1;
//如果把p1指向空,Person对象又没有了指向它的强指针。会被废弃。
//此时通过p2已经找不回那个Person对象了。而且p2会被编译器置为nil
p1 = nil;
NSLog(@"p2: %@", p2); //输出为null
}
#import
#import "Person.h"
@interface Dog : NSObject{
//主人 默认是__strong,手动设为__weak
Person __weak *_owner;
}
-(void)setOwner:(Person *)owner;
@end
一张图解释:
@autoreleasepool {
/*
取得非自己生成并持有的对象
*/
id __strong obj = [NSMutableArray array];
/*
因为变量obj为强引用
所以自己持有对象
并且该对象有编译器判断其方法名后
自动注册到autoreleasepool
*/
}
/*
因为变量obj超出其作用域,强引用失效
所以自动释放自己持有的对象
同时,随着@autoreleasepool块结束,
注册到autoreleasepool中的所有对象
被自动释放
如果所有者不怎在,所以废弃对象
*/
id obj = [[NSObject alloc] init];
@autoreleasepool {
id __autoreleasing obj2 = obj;
NSLog(@"%@",obj2);
}
NSLog(@"%@",obj);
两个NSLog输出是一样的。可以看出变量obj指向的对象没被废弃,因为obj2废弃后这个对象还有obj这个强指针指向。
//别忘了,默认是__strong
id obj = [[NSObject alloc] init];
for (int i = 0; i < 10000; i++)
{
id __autoreleasing obj2 = obj;
}
像这样没有明显地使用@autoreleasepool,就是把10000个obj2注册到main runloop下的autorelease pool里,然后执行完这个for循环后再一次性废弃这10000个__autoreleasing修饰的变量。一般来说,当需要注册到autorelease pool的变量太多的话,最好自己手动创建@autoreleasepool,不然会使得main runloop里的autorelease pool太撑,降低它的性能。
id obj = [[NSObject alloc]init];
for (int i = 0; i < 100000000; i++)
{
@autoreleasepool
{
id __autoreleasing obj2 = obj;
}
}
基本上四个修饰符表现出来的基本特性就是这样子了。但是它们真正的内涵可远远不止于此,想要真正理解它们,还有很多内容需要知道。毕竟看起来越简单的东西,背后一定有着更复杂的实现。我们享受这ARC方便好用的同时,它背后一定有着更复杂的实现机理。现在可以整理一下思路,准备继续进发吧。
self.property = [[NSObject alloc] init];//property的定义是@property (nonatomic, retain) NSObject *property;
self.name = [NSString stringWithFormat:@"%@ %@", firstName, lastName];
以及在自己写的函数中:
+ (MyCustomClass *) myCustomClass
{
return [[MyCustomClass alloc] init]; // 不用 autorelease
}
这些写法都是 OK 的,也不会出现内存问题。
NSObject * a = [[NSObject alloc] init];
self.property = a;
//[a release]; 我们不需要写这一句,因为 ARC 会帮我们把这一句加上
{
id __strong obj = [NSMutableArray array];
}
这里补充《OC高级编程--ios与os x多线程和内存管理》这本书上贴出的关于array的源码:
+ (id) array
{
return [[NSMutableArray alloc] init]; } //返回一个retained return value,但编译器扫描到当前方法是array方法,会作出优化
id obj = objc_msgSend(NSMutableArray,@selector(array));
objc_retainAutoreleasedReturnValue(obj); // 相当于调用retain
objc_release(obj);
+ (id) array
{
id obj = objc_msgSend(NSMutableArray, @selector(alloc)); //用一个__strong来接收
objc_msgSend(obj, @selector(init));
return objc_autoreleaseReturnValue(obj); //把obj注册到autorelease pool,相当于MRC下调用autorelease,到时候会自动为这个对象的引用计数-1,ARC下就是把obj这个强指针废弃掉,对象没有强指针指向,紧接着废弃这个对象
}
虽然说ARC模式下,我们不可以自己手动执行release和retain之类的各种方法,但其实是因为编译器会帮我们插入这方面的代码来管理对象。因此可以看到编译器改写后的代码具有这些方法的影子。
id __weak obj2 = [[NSMutableArray alloc] init];//会报对象被废弃的警告,随后打印obj2得到的是null
id __weak obj3 = [NSMutableArray array];//不会报错
现在可以就这个现象解释一下了。已知第一个方法返回一个retained return value,返回的时候如果没有强指针指向它,会一出生就废弃。因为编译器会自动插入一条release操作式这个对象的引用计数-1,假如没有强指针指向它为它的引用计数+1,这时候引用计数就会减为0,对象被废弃。
{
id __weak obj1 = obj;
NSLog(@"%@",obj1);
}
该代码会被编译器转化成如下形式:
/*编译器的模拟代码*/
id obj1;
objc_initWeak(&obj1, obj);
id tmp = objc_loadWeakRetained(&obj1);
objc_autorelease(tmp);
NSLog(@"%@",tmp);
objc_destoryWeak(&obj1);
与赋值时相比,在使用附有_weak修饰符的变量的情形下,增加了对objc_loadWeakRetained函数和objc_autorelease函数的调用。这些函数的动作如下。
{
id __weak obj1 = obj;
NSLog(@"%@",tmp);
NSLog(@"%@",tmp);
NSLog(@"%@",tmp);
}
如此会有三个变量注册到autoreleasepool中,性能非常不友好。下面的使用可以解决这样的问题:
{
id __weak obj1 = obj;
id tmp = obj1;
NSLog(@"%@",tmp);
NSLog(@"%@",tmp);
NSLog(@"%@",tmp);
NSLog(@"%@",tmp);
}
对于retained return value考虑以下代码:
@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);
可以看到对象被注册到autoreleasepool,这没什么好奇怪的,因为我们使用了__autoreleasing修饰的obj来接收。
@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);
为了方便,再贴上array的模拟代码:
+ (id) array
{
id obj = objc_msgSend(NSMutableArray, @selector(alloc)); //用一个__strong来接收
objc_msgSend(obj, @selector(init));
return objc_autoreleaseReturnValue(obj); //把obj注册到autorelease pool,相当于MRC下调用autorelease,到时候会自动为这个对象的引用计数-1,ARC下就是把obj这个强指针废弃掉,对象没有强指针指向,紧接着废弃这个对象
}
由此可以看到,编译器在这里再一次做了优化。一般情况下array方法的返回值会通过调用objc_autoreleaseReturnValue把对象注册到autorelease pool,此时,编译器插了一条objc_retainAutoreleasedReturnValue();通过上面的学习知道这会跳过把对象注册到autoreleasepool的操作。紧接着是__autoreleasing修饰符产生的objc_autorelease(obj)方法,把对象注册到autorelease pool。这样,就可以避免因为__autoreleasing修饰符和array方法的搭配使用,而把一个对象注册到autorelease pool两次。
看到这里是不是觉得有点混乱,一会引用计数,一会强指针。补充一下说明吧:
在MRC中,是通过引用计数来管理对象的生命周期,并没有强弱指针的概念,还是第一篇文章的例子:
int main(int argc, const charchar * argv[]) {
id *obj1 = [[NSObject alloc] init];
NSObject *obj2 = obj1;
//obj2也指向了这个NSObject对象,但NSObject引用计数不会自动+1,程序员有义务为其引用计数+1
[obj2 retain];
//既然有了[obj2 retain],那么obj2用完之后也要有[obj2 release],retain和release方法需要成对出现。
[obj2 release];
//此时,仍然可以通过obj2指针使用到NSOjbect对象,但这是不合规范且危险的
NSLog(@"NSObject obj2:%@", obj2);
[obj1 release];
//到这里,NSObject对象的引用计数减为0了
//此时如果再访问NSObject对象就会报错
//NSLog(@"NSObject retaincount: %ld", (unsigned long)[obj2 retainCount]);
//所以这时候obj1和obj2都成为野指针了,这个问题会在ARC中得到解决。也可以手动设置obj1=nil,obj2=nil解决
return 0;
}
创建一个变量指针指向一个已有对象的时候,如obj2指向NSObject对象,编译器也不会自动为对象的引用计数+1,因此,本着负责任的态度,都需要为它的引用计数+1(retain),用完再-1(release)。此外,release并不代表obj2不再指向这个对象,仅仅是让引用计数-1。那如果不是指向一个已经的对象,而是创建对象的时候,就要分以下两种情况了。
当使用alloc/new/copy/mutableCopy方法时:NSObject *obj1 = [[NSObject alloc] init]。这样创建的对象在创建的时候引用计数就置1了,因此不需要额外地使用[obj1 retain],用完调用[obj1 release]即可。
当使用类方法时:如id obj = [NSMutableArray array]。已知类方法内部的实质也是调用alloc/new/copy/mutableCopy来创建对象,因此一创建它们的引用计数也已经为1了,但类方法的区别在于,它会把对象注册到autorelease pool,用完这个对象不用为其release。这个pool一般是runloop下的pool,它会在某个时候自己调用drain,把注册到里面的对象引用计数-1。如果是注册到自己创建的pool,那就要记得自己调用drain方法。我们自己编写类方法的时候,也应该遵守苹果的这个规范,把对象注册到autorelease pool。
在ARC中,有了强弱指针,就不用再考虑引用计数的问题了,引用计数编译器会自动帮我们处理。不然这个机制就不会叫自动引用计数了。我们只需要简单地考虑有没有强指针指向这个对象。多一个强指针指向这个对象时,编译器自动帮这个对象的引用计数+1,少一个强指针指向这个对象时,编译器自动帮这个对象的引用计数-1。如果没有强指针指向这个对象也就意味着这个对象引用计数为0,需要废弃了。而对于autorelease pool的问题,它仍旧是需要的,因为它在处理返回值问题以及批量处理对象问题上都能发挥很好的作用。所以也就有了__autoreleasing修饰符了,这个修饰符能把对象注册到autorelease pool里。但就算没有写这个修饰符,在类方法的返回值上,编译器也会机智地根据实际需要自动帮我们把对象注册到autorelease pool里(看接收的对象,不一定注册的)。而对于autorelease pool里的对象,也可以理解成有一个会自动释放的强指针指向它,这个强指针会随着它所在的pool结束而废弃。