文章目录
搞清楚一些名词
内存泄漏:
内存泄漏(Memory Leak)是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
ARC
使用ARC时,引用计数其实还存在,释放和保留的操作由系统替我们完成,MRC下的 retain、release、autorelease、delloc这些方法无法调用。
ARC管理内存时不是通过普通的消息派发,而是调用与以上方法等价的底层函数。
所有权修饰权修饰符
id类型和对象类型 和其他c语言类型不同,需要加上所有权修饰符,默认是__strong
四种修饰符
__strong
__weak
__unsafe_unretained
__autoreleasing
__strong
强引用修饰符
id __strong obj = [[NSObject alloc] init];
= [NSMutableArray array];
不论调用哪种方法,强引用修饰的变量会持有该对象,如果已经持有则引用计数不会增加。
- 超出此变量的作用域时,强引用失效。
- 自动释放持有的对象。
- 对象的所有者不存在,废弃对象。
怎么算不再持有
指向对象A的指针指向了其他对象B,表示该指针持有了另外的对象B,在MRC下需要先释放对象A,然后持有对象B。
即
id obj = [[NSObject alloc] init]; 对象A
[obj release];
此时对象A的引用计数为0,系统将自动释放A。
id obj2 = [[NSObject alloc] init];对象B
obj = obj2; obj引用对象B
[obj retain]; obj持有对象B
在ARC下,俩个强引用的对象可以直接赋值。
__strong、__weak __autoleaseing可以保证变量自动初始化为nil.
MRC下的set方法
我们不注意可能会写成这样。
- (void)setObject:(id)object {
[_object release];
_object = [object retain];
}
假如object和原来的_object是一个对象。那么在没赋值前 object会被释放,在赋值和retain会出错。
更安全的方式是先保留新值,再释放旧的值。
__strong默认语义,保留此值。
互相引用
@interface Test:NSObject {
id __strong obj_;
}
@end
@implementation Test
- (id)init {
self = [super init];
return self;
}
- (void)setObject:(id __strong)obj {
obj_ = obj;
}
{
id test0 = [[Test alloc] init]; TestA
id test1 = [[Test alloc] init]; TestB
[test0 setObject: test1]; test0持有TestB、TestA。
[test1 setObject: test0]; test1持有TestA、TestB
}
超出变量作用域。
TestA的持有者test0失效。
TestB的持有者test1失效。
此时TestA被TestB的成员变量持有。
TestB被TestA的成员变量持有。
俩者相互持有,没有释放,内存泄漏。
__weak修饰符
循环引用会导致内存泄漏。而内存泄漏如开头所说,指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放。
循环引用意味着互相持有。
意味着俩个对象的引用计数都为1.
释放其中一个,另一个的对象的持有者释放,引用计数也变为0,发生崩溃。
对象的引用计数是记录在一张表上的,不在对象本身或者指针中,系统通过访问这张表来确定是否释放该对象。
weak提供弱引用,弱引用不持有对象
将上面相互引用例子中的成员变量变为weak,即可避免相互引用。
@interface Test:NSObject {
id __weak obj_;
}
- (void)setObject:(id __strong)obj
weak还有个作用。在持有某对象的弱引用时,若该对象被废弃,则此若引用将自动失效且处于nil被赋值的状态(空弱引用),而低版本的__unsafe_unretained修饰符不会赋值为nil,我们能够访问已经释放的对象,因为那块内存碰巧还没有被清空。
ARC有效的__autoreleasing修饰符
在ARC下,我们不能创建NSAutoreleasePool对象了,但是可以使用@autorelease 来替代。
作为替代,在MRC下我们需要显示调用autorelease方法,在@autorelease中,只需要加上__autoreleasing关键字就ok
只是这个关键字和strong一样并不需要显式的写。
编译器会检查方法名是否以alloc/new/copy/mutableCopy开始,如果不是,则自动将返回值对象注册到autoreleasepool中。
可以看到,自动释放池中有__NSArrayM我断点测试了一下,和obj0的地址相同。
如果是以alloc开头
我们写的这个@autoreleasepool也算一个releases pending。这里打印2个releases peding。
如果去掉外面的@autorelease,就成了一个。
这样看似乎是对的,可以变量多了呢?这里加入到pool中却只有一个对象
这个问题暂时没法解决,来看看苹果的源码吧
ARC的实现
id __strong obj = [[NSObject alloc] init];
这段代码在是如何工作的呢?
//ARC下模拟代码
id obj = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(obj, @selector(init));
objc_release(obj);
ARC在这里自动插入了release代码
如果使用alloc/new/copy/mutableCopy之外的方法。
{
id __strong obj = [NSMutableArray array];
}
伪代码如下
id obj = objc_msgSend(NSMutableArray, @selector(array));
objc_retainAutoreleasedReturnValue(obj);
objc_release(obj);
可以看到release也被添加了,可是中间的objc_retainAutoreleasedReturnValue(obj)是啥呢?
这段代码的作用是优化程序运行,它的作用是持有对象,持有的对象是来自动释放池的。因为obj是强引用,所以要持有这个对像,这方法将来自自动释放池的对象引用计数+1.使用alloc/new/copy/mutableCopy之外的方法就会插入这条。
如果是alloc之类的方法呢?
我们继续看看array这个方法的实现
{
return [[NSMutableArray aloc] init];
}
伪代码
+ (id) array {
id obj = objc_msgSend(NSMutableArray, @selector(alloc));
objc_msgSend(obj,@selector(init));
return objc_autoreleaseReturnValue(obj);
}
这俩个方法就比较神奇了。objc_autoreleaseReturnValue(obj)、objc_retainAutoreleasedReturnValue(obj)
这俩个函数都会在对象作为返回值时注册到pool中,但是objc_autoreleaseReturnValue()与objc_autorelease不同,它会检查调用该函数的方法或调用方的执行命令列表,如果方法或函数的调用方调用了这个函数后接着又调用了objc_retainAutoreleasedReturnValue(),那么就不将返回的对象注册到pool中,而是之间传递到调用方。
objc_retainAutoreleasedReturnValue()不同于objc_retain,即便对象不注册到pool中,它也能够正确的获取对象,而不执行retain,这样一来相当于省去了函数返回时调用autorelease和retain俩个函数而不影响引用计数的正确语义。这俩个函数使对象跳过了注册pool的步骤。
__weak修饰符
{
id __weak obj1 = obj;
}
//伪代码
id obj1;
objc_initWeak(&obj1, obj); //初始化变量
objc_destroyWeak(&obj1); //释放变量
objc_initWeak做了什么呢?
obj1 = 0;
objc_storeWeak(&obj1, obj); //初始化0后,将obj作为参数调用objc_storeWeak函数。
//那objc_destroyWeak(&obj1)呢?
会把0作为参数调用objc_storeWeak函数。
objc_storeWeak(&obj1,0);
objc_storeWeak有俩个参数。第一个参数是__weak修饰的变量,第二个参数的地址作为第一个参数的value。key是第一参数的地址。将一对key/value注册到weak表中,如果第二个参数为0,则把变量从weak表删除。
weak表与引用计数表相同,作为散列表被实现。如果使用weak表,将废弃对象的地址作为键值进行搜索,就能高速度获取对应附有__weak修饰符的变量地址。一个对象可以同时赋值给多个__weak修饰符的变量,所以对于一个value,有多个key。
对象的废弃
objc_release在没有被持有的时候执行
1.objc_release
2.因为引用计数为0,执行delloc
3._objc_rootDealloc
4.object_dispose
5.objc_destructInstance
6.objc_clear_dellocating
最后一个函数objc_clear_dellocating的动作:
1.从weak表中获取废弃对象的key记录,找出所有的,value是废弃对象的key(这断句好难)
2.将包含在记录中的所有附有__weak修饰符变量的地址,赋值为nil。(把刚找出来的key的__weak变量赋值为nil)
3.从weak表删除该记录。(将key/value键值对删掉)
4.从引用计数表中删除废弃对象的地址为键值的记录(引用计数表也是一样,废弃对象的地址为key,value是引用计数)。
由此可知,如果有很多__weak变量,会消耗cpu资源。所以一般在避免循环引用时使用__weak.
{
id __weak obj = [[NSObject alloc] init];
}
//伪代码
id obj;
id tmp = obj_msgSend(NSObject, @selector(alloc));
objc_msgSend(tmp,@selector(init));
objc_initWeak(&obj, tmp);
//tmp对象无人持有。
objc_release(tmp)
objc_destoryWeak(&object);
__weak的另外一个功能
{
id __weak obj1 = obj;
NSLog(@"%@", obj1);
}
使用__weak变量, 即是注册到autoreleasepool中的对象。
伪代码
id obj1;
objc_initWeak(&obj1, obj);
id tmp = objc_loadWeakRetained(&obj1);
objc_autorelease(tmp);
NSLog(@"%@", tmp);
objc_destroyWeak(&obj1);
与被复制相比,使用weak变量增加了objc_loadWeakRetained()和objc_autorelease()
1.objc_loadWeakRetained函数取出__weak变量引用的对象然后retain。
2.objc_autorelease()将其注册到autorelease中。
但是我测试了一下,当前版本中,在ARC无效的情况下该功能正常。
当ARC有效时
关闭ARC后
可以说这本书太老了
#############更新###########
// 切换到你文件路径下
cd Path
// 利用 main.m 生成中间码文件 main.ll
clang -S -fobjc-arc -emit-llvm main.m -o main.ll
查看中间代码
void weak2Function() {
id obj = [NSObject new];
__weak id obj1 = obj;
NSLog(@"%@",obj1);
}
可以查看到底调用了什么
如图所示:我们将被调用的方法列举出来
id obj = objc_msgSend(NSObject, @selector(new));
objc_initWeak(obj1, obj);
id temp = objc_loadWeakRetained(obj1);
NSLog(@"%@",temp);
objc_release(temp);
objc_destroyWeak(obj1);
objc_storeStrong(obj, null);
使用了弱引用变量obj1,在使用弱引用变量之前,编译器创建了一个临时的强引用对象,在用完后立即释放。
我们发现autorelease没有被调用。这是ARC下,如果转为MRC呢?
结果发现
MRC下的调用似乎和ARC一致。
autorelease
@autoreleasing{
id __autorelease = [[NSobject alloc] init];
}
//伪代码
id pool = objc_autoreleasePoolPush();
id obj = objc_magSend(NSObject, @selector(alloc));
objc_msgSend(obj, @selector(init));
objc_autorelease(obj);
objc_autoreleasePoolPop(pool);
@autoreleasing{
id _autoreleasing obj = [NSMutableArray array];
}
//伪代码
id pool = objc_autoreleasePoolPush();
id obj = objc_msgSend(NSMutableArray, @selector(array));
objc_autorelease(obj);
objc_autoreleasePoolPop(pool); //析构pool
代理中的弱引用
我们通常写的delegate都用的是weak属性。
表明弱引用,非拥有关系。
我们通常将view的delegate设为viewController。
如果用strong修饰,delegate强引用viewCotroller。
也就是说 view的持有者是ViewController, ViewController的持有者是view。