内存管理原理----《Objective-C高级编程:iOS与OS X多线程和内存管理》读书笔记

《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统一存储所有对象的引用计数
GNUstep使用的是前一种思想,在这里就不详述了。
苹果的实现方式是后一种----runtime采用散列表来管理引用计数,存储形式如下图所示:
内存管理原理----《Objective-C高级编程:iOS与OS X多线程和内存管理》读书笔记
苹果在实现时,凡是使用引用计数的方法,包括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修饰符。


内存管理原理----《Objective-C高级编程:iOS与OS X多线程和内存管理》读书笔记

上一篇:误删硬盘分区--装系统时不小心删了一个分区--DiskGenius


下一篇:poj 2411 状态压缩dp