Objective-C内存管理

文章目录

1.非arc环境

特点:

(1)没有默认修饰符

(2)谁持有,谁释放,不持有,不释放

(3)释放后若引用计数为0,则废弃对象,释放内存

(4)作用域的规则

1{
2  NSString *anObj = [[NSString alloc] init];
3}

手动生成对象后,引用计数为1

超出作用域后,对象指针anObject被置为nil,但它指向的对象引用计数仍为1,造成内存泄漏。

自动释放池

整个程序一开始就是运行在一个巨大的自动释放池中,释放池可以嵌套,可以手动生成和释放

手动生成的释放池,在作用范围内管理带有autorelease的对象,相当于持有它们,并加入管理队列,当释放掉手动生成的释放池时,将池子中的所有自动释放对象引用计数减1,并释放对象占用的内存,如果自动释放对象被持有而又没有释放,超出释放池后会继续存留,当它是池中创建的局部变量指针时,会造成内存泄漏。

如何判断是否持有

(1)初始化时,等号右侧是否调用alloc/new/copy/mutableCopy等关键字,且不加入自动释放池

(2)取得非持有对象后,是否调用retain方法

点语法与前置下划线的影响

使用点语法会调用setter与getter,并执行属性,比如retain,将自己生成的对象赋予含有retain属性的指针,引用计数会加1,没有自定义访问器的时,使用默认,而使用前置下划线的话,不用调用访问器,但因为我一开始就使用arc,没有测试过retain,不能确定使用前置下划线时,是否会引起引用计数增加,因为非arc环境下,没有修饰符,那么理论上使用前置下划线不会引起应用计数增加,可当作取得对象,而不持有

持有,释放与废弃

表现为引用计数的增减,初始化并持有,表现为引用计数为1,立即释放后引用计数为0,任何一次释放后,若引用计数为0,则废弃,表现为,找到内存地址并free,但没有自动将指针置为nil,需要手动加入自动释放池的对象由自动释放池管理

2.arc环境

进入arc后,需要暂时丢掉引用计数的概念

特点

(1)使用修饰符(两个前置下划线)

__strong

所有对象的默认修饰符,表示持有

__weak

相当于取得对象而不持有

__unsafe_unretained

iOS4以及snow leopard下的weak修饰符的替代,编译器不管理它的内存对象,使用错误时会造成崩溃

__autoreleasing

取得非自己生成并持有的对象,如NSError** error,我们在使用NSError时,使用指向指针的指针,NSError ** error = NSError *__strong __autoreleasing *error

(2)作用域规则改为超出作用域后对象指针置为nil,检查对象是否仍被持有,不被持有则释放并废弃

(3)不能显式地调用dealloc,但可以重写

持有,释放与废弃

持有者可以是自己定义的指针也可以是某个对象的指针类型的成员变量

若对象被持有,则不释放

所有指向对象的指针被置为nil或者变为指向其他对象时,持有者(指针)不存在,释放对象并废弃

超出作用域后,持有者置为nil

循环引用的情况发生在自己定义的两个指针超出作用域后置为nil,但它们指向的两个对象的指针类型的成员变量相互引用对方,结果就是应该被释放的两个对象相互持有,结果无法释放,这种情况下如果将指针类型的成员变量修饰符改为__weak,则为取得对象而不持有,可以解决这种情况,循环引用可以形成一个大圆圈,不仅限于两个对象之间循环引用,使用blocks通常会造成循环引用自身,虽然块遵循作用域的规则,但在块中频繁访问自身的成员变量时,也会造成循环引用,解决的办法是strongSelf,weakSelf大法。

但基于持有,可以设计重用对象。

这时,内存泄漏主要发生在循环引用和大量且频繁的内存占用与释放,这都是我们应当避免的。

自动释放池

程序依旧运行于一个巨大的自动释放池中,但不能手动生成和管理释放池了,取而代之,使用

@autoreleasepool{}来包含需要自动释放的对象,weak对象可当作带有引用计数而又归自动释放池管理的对象

点语法与前置下划线

点语法依旧和访问器相关,也会在属性为strong时引起引用计数的增加,这个可以在混编时,使用CFGetRetainCount查看到,但前置下划线的修饰符与属性中定义的引用类型相同。

3.混编

处理core foundation对象时,我们仍旧需要手动管理内存申请与释放,它们同样遵循作用域规则,问题主要在于foundation对象与core foundation对象的相互转换,比如使用CoreGraphics绘图后直接获取位图并赋予CALayer的contents,我们需要关注几个关键字

__bridge id

不引起转换后对象引用计数变化,无需手动释放

__bridge_retain id

相当于执行引用计数增1的操作,需要手动释放

__bridge_transfer CFTypeRef

相当于执行引用计数减1后,交付给foundation对象

将foundation对象转换为core foundation时,可视为引用计数从0开始,直接桥接不增加引用计数,相当于取得对象而不持有,使用了retain的话,相当于取得对象并持有,引用计数从1开始

这时内存泄漏可能发生在core foundation对象没有释放完全,也就是说直接桥接时引用计数大于0,或者使用transfer时,引用计数大于1。