Pro Multithreading and Memory Management for iOS and OS X是iOS与OSX平台开发者的神书之一不解释,适合时常翻阅,虽然说ARC时代对于内存管理开发者不需要过多的参与,编译器帮我们做了绝大多数工作,但是适当的理解还是非常有必要的,我自己也遇到过一些诡异的bug是因为内存管理不当造成的,同时感觉内存管理这块也是面试时候的重点哈哈哈哈所以有必要深入了解一下.
这本书分三章:内存管理,Block,GCD,花了一段时间把内存管理篇读完了,写点总结权当巩固一下知识.
1.引用计数
不同于java等语言的垃圾回收机制,苹果引入了引用计数的改变,简单的理解就是一个对象被人持有的时候,引用计数retainCount + 1,这个引用关系没有的时候就-1,当引用计数为0的时候则认为对象没有被人持有,就释放掉这个对象了.一般当我们调用alloc,new,copy,mutableCopy
开头的方法的时候我们自己都会持有这个对象,自己可以持有自己生成的对象,同时也能够持有别人生成的对象,比如:
//obj持有自己生成的对象
id obj = [[NSObject alloc] init];
//obj2持有obj这个对象
id obj2 = obj;
2.ARC 自动引入计数
顾名思义,自动引用计数(相对于MRC手动引用计数),是Xcode的编译器特性之一,编译器会在适当的时候帮我们插入内存管理代码(不是这么简单还有其他一些复杂的优化操作),让我们避免了花费大量精力去考虑内存管理.
先了解一下对象的所有权修饰符,在ARC环境下,有四种所有权修饰符:
__strong
__weak
__unsa__unretained
__autorelease
我们平时这样定义一个属性:
@property(nonatomic, strong) UIView *view;
其中的strong就是内存管理关键字,实际上我们在定义属性的时候使用的strong/weak/assign等都是被转化成这些修饰符,下面是对应的转化关系:
assign ------> __unsafe__unretained修饰符
copy ------> __strong修饰符(赋值的是被复制的变量)
retain ------> __strong修饰符
strong ------> __strong修饰符
unsafe_unretained ------> __unsafe__unretained修饰符
weak ------> __weak修饰符
使用以上关键字就相当于直接给属性加上了对应的修饰符,copy除外,copy赋值是通过copyWithZone:
方法复制源对象的修饰符.
2.1 __strong
修饰符
__strong
是强引用的意思,它是id类型跟对象类型的默认修饰符,也就是说默认会加上__strong
修饰符,除非你去显式的指定__weak
等修饰符,这种修饰符修饰的对象会在超出它的作用域的时候释放掉,或者说如果是通过另外一个强引用对象赋值产生的强引用,会在它引用的对象为nil的时候释放掉.
__strong,__weak,__autoreleasing
这三种修饰符可以保证对象在初始化的时候把对象初始化为nil,这一点是跟__unsafe__retained
很大的不同.
2.2 __weak
修饰符
id __weak obj = [[NSObject alloc] init]
__weak
是iOS5之后新增的,即为弱引用,它是为了解决__strong
修饰符导致循环引用来引入的,所谓循环引用就是A,B对象相互引用,就很容易发生了内存泄露,当然还有更罕见的A对象自己持有自己导致的循环引用.
弱引用不能持有一个实例对象,刚才的那句代码对象会立即释放,因为弱引用不持有对象,所以在对象超过作用域的时候就会立刻把对象释放掉,于是就打破了引用循环.
weak还有一个好处,当一个对象释放掉的时候,会立刻把对象置nil(assign对应的修饰符是__unsafe__unretained
则不会).
weak实现
__weak修饰符的实现其实比较复杂,当我们初始化一个weak修饰的对象,运行时是调用objc_initWeak(&obj1,obj)
,obj1
是我们初始化对象后赋值的对象,obj
则是该对象本身,这个函数会继续调用objc_storeWeak(&obj1, obj)
函数,然后把第二参数obj作为key,第一参数&obj1作为value存储到weak表中,这样的话在废弃对象的时候通过将要废弃的对象的地址作为键值从表中查询就能够快速的查找到对应的对象了.
weak销毁
然后在对象作用域结束,就调用了销毁函数objc_destoryWeak(&obj1)
,这个函数具体会做哪些事呢?
(1). objc_release
(2). 引用计数为0,执行dealloc
(3). _objc_roowDealloc
(4). object_dispose
(5). objc_destructInstance
(6). objc_clear_deallocating
其中最后一个函数objc_clear_deallocating
又会做以下操作:
a. 从weak表中通过废弃对象的内存地址查找记录
b. 将查找到的所有__weak修饰的记录的地址赋值为nil.
c. 从weak表删除记录
d. 通过废弃对象的地址从引用计数表中查找到记录并且删除.
我们可以看到,通过这些操作,__weak修饰的对象在销毁的时候就能赋值为nil了,但是同时我们也应该看到使用weak是比较消耗cpu资源的,所以我们应该避免过多使用weak,而仅仅使用它来避免循环引用,这也是实现weak的目的所在.
2.3 __unsafa__unretained
修饰符
这玩意可以翻译为不安全不持有的修饰符,反正我是开发工作中是没有接触到过,它跟weak相同的是都不持有对象,所以生成的对象会立刻被释放掉.
2.4 __autoreleasing
修饰符
在ARC下不能用NSAutoreleasePool
来创建自动释放池,可以通过@autoreleasepool(没错就是程序入口的那玩意)来创建,使用了这个修饰符类似于在MRC下调用了autorelease
方法.
还记得我们开头说的调用alloc,new,copy,mutableCopy
方法会持有生成的对象么?那么当我们不是通过这些方法创建的对象呢?比如:
id obj = [NSArray array];
事实是如果编译器检查到对象不是通过以上四个方法创建的,会自动把这个对象注册到autoreleasepool
,那么自动释放池里面的对象会什么时候释放呢?这里其实牵扯到runloop的知识,自动释放池会注册到runloop中,可以把runloop理解为一个不停开始-休眠-开始
的循环,当本次循环执行完毕,下次循环开始的时候如果检测到对象没被引用就会释放这个对象(更多runloop相关内容可以看百度知道团队的runloop视频).
再回到weak,其实在访问__weak
修饰的对象的时候,实际上必定会访问注册到autoreleasepool
的对象.这句话怎么理解呢?
因为weak是弱引用,所以在访问的时候有可能对象已经被废弃了,所以苹果的做法是通过把要访问的对象注册到autoreleasepool
中,在@autoreleasepool{}
结束之前都能确保对象一直存在咯.
以上是自己读书下来的一些笔记,大致大致总结了一下各种关键字底层做了些什么,有错误的地方欢迎指出,后续再有新的心得会在这个博客下面继续记录下来~~