《Objective-C高级编程:iOS与OS X多线程和内存管理》读书笔记
作者:wangzz
转载请注明出处
如果觉得文章对你有所帮助,请通过留言或关注微信公众帐号wangzzstrive来支持我,谢谢!
一、前言
这本书由日本人Kazuki Sakamoto和Tomohiko Furumoto所著,主要讲了ARC、Blocks、GCD三个模块。总体来说,书的内容讲的挺深的,小日本写的东西还真是不错。作者一直试图从原理上阐述ARC、block、GCD的实现机制,不像大部分国内相关书籍中只介绍其然,这是一本探索所以然的书。鉴于上述三个模块是每个试图在iOS开发上有所造诣的同学必须掌握的东西,强烈建议已经有一定Objective-C功底的同学看看。
从事iOS开发以来一直都是使用手动内存管理,因此对引用计数的理解还是比较透彻的。直到开发拉手团购6.0版的时候,开始使用ARC,上手后的感觉是以后的内存管理都是ARC的天下。在这本书中,作者也着重介绍了手动内存管理及其实现。书中讲的内容大部分之前都看过,不过自己看的不够系统。断断续续花了二十天左右时间读完这本书,还是收获不小,下面将自认为值得记住的地方列举出来:
二、引用计数原理
iOS中说到内存管理,不管是手动还是ARC,都离不开引用计数,我一直很好奇引用计数到底是怎么存储的呢?可能的实现方式有两种:
- 鉴于Objective-C对象都有引用计数,所以每个对象都应该存储自己的引用计数
- runtime统一存储所有对象的引用计数
苹果的实现方式是后一种----runtime采用散列表来管理引用计数,存储形式如下图所示:
苹果在实现时,凡是使用引用计数的方法,包括retainCount、retain、release方法都调用了同一个方法,该方法的实现原理简化后如下所示:
int _CFDoExternRefOperation(uintptr_t op, id obj) { CFBasicHashRef table = 取得对象的散列表(obj); int count; switch(op) { case OPERATION_retainCount; count = CFBasicHashGetCountOfKey(table, obj); return count; case OPERATION_retain: CFBasicHashAddValue(table, obj); return obj; case OPERATION_release: count = CFBasicHashRemoveValue(table, obj); return 0 == count; } }这一个方法就解释了引用计数的实现原理!
苹果这种实现方式的优点是:
不用再每个对象内存块中考虑引用计数所站的内存;
引用计数表中存储的有各个对象的内存地址,可以直接通过各条引用技术追踪到对应的对象内存块,在调试的时候很有用。
三、__autoreleasing关键字
看我完这本书后,对该修饰符有了更深的理解,有以下几个方面:
1、ARC下使用__autoreleasing修饰符修饰的变量会被注册到autoreleasepool中,和非ARC下调用autorelease方法的效果相同;
2、当方法名不是alloc/new/copy/mutableCopy时,使用return返回的对象强引用会被自动注册到autoreleasepool。比如:
+ (id)array { id obj = [[NSArray alloc] init]; return obj; }上述代码中,因为没有指定所有权修饰符,id obj就是默认的id __strong obj,即obj是强引用,这时,obj就会被添加到autoreleasepool中。
3、访问附有__weak修饰符的变量时,该变量会被自动注册到autoreleasepool中,比如:
id __weak obj1 = obj0; NSLog("class=%@", [obj1 class]); //等价于以下代码 id __weak obj1 = obj0; id __autoreleasing tmp = obj1; NSLog("class=%@", [tmp class]);这种等价是编译器自动做的转换,原因是__weak修饰符支持有对象的弱引用,在访问引用对象的过程中,该对象可能被释放。而如果将该对象加入到autoreleasepool中,在pool被释放之前,tmp对该对象的引用都是有效的。
4、_objc_autoreleasePoolPrint()函数
该函数属于私有函数,可以打印出注册到调用函数处所属的autoreleasepool中的对象。
四、__weak关键字
这部分需要着重介绍的是__weak关键字。
我们知道,当一个__weak类型的指针指向的对象被释放时,该指针会自动被置成nil,因此__weak关键字修饰的指针又被称为智能指针。那么这个功能是如何实现的呢?
1、编译器做的事
先看下面这段代码:
{ id __weak obj1 = obj; }会被编译器编译成下面的模拟代码:
id obj1; objc_initWeak(&obj1, obj); objc_destoryWeak(&obj1);即编译器会通过objc_initWeak函数初始化__weak修饰的变量,当变量的作用域结束后会通过objc_destoryWeak函数释放该变量。objc_initWeak函数实际干的活是:
objc1 = 0; objc_storeWeak(&obj1, obj);这里是先将指针objc1置成0,再调用objc_storeWeak函数使得obj1指向obj对象。
接下来的objc_destoryWeak函数的实际操作如下:
objc_storeWeak(&obj1, 0);也就是说,让obj1指针指向的内容变成空。
总结一下,刚才的整个过程模拟代码如下:
id obj1; obj1 = 0; objc_storeWeak(&obj1, obj); objc_storeWeak(&obj1, 0);
2、__weak实现原理
实际上,objc_storeWeak函数会把第二个参数的对象的地址作为key,并将第一个参数(__weak关键字修饰的指针的地址)作为值,注册到weak表中。如果第二个参数为0(说明对应的对象被释放了),则将weak表中将整个key-value键值对删除,这就是__weak关键字的核心思想!
weak表和引用计数表类似,都是通过hash表实现的。如果使用weak表,将被释放的对象地址作为key去检索,就能很高效的获取对应的指向该对象的类型为__weak的指针变量的地址。同时很容易理解,一个对象可能有多个__weak指针指向,因此一个对象地址key可能对应多个值。
在调用对象的release方法时,会在其中一步调用objc_clear_deallocating函数,该函数会执行以下操作:
- 以当前对象的地址作为key,从weak表中获取对应的值----指向该对象的__weak类型的指针变量;
- 将取到的所有指针变量的值赋值为nil;
- 从weak表中删除该key对应的整条记录。
根据以上步骤,前面介绍过的__weak关键字修饰的对象指针所指向的对象被释放时,指针被置为nil就可以实现了。同时由此可知,如果大量使用附有__weak修饰符的变量会消耗响应的CPU资源,因此,应该尽量少使用__weak修饰符。