1.引用计数式内存管理的思考方式
- 自己生成的对象,自己持有
- 非自己生成的对象,自己也能持有
- 不在需要自己持有的对象时释放
- 非自己持有的对象无法释放
2.alloc方法
+ alloc
+ allocWithZone:
class_creatInstance
calloc
调用alloc方法首先调用allocWithZone:类方法,然后调用class_creatInstance函数,最后调用calloc来分配内存块。
3.retainCount/retain/release 方法
- retainCount
__CFDoExternRefOperation
CFBasicHashGetCountOfKey
- retain
__CFDoExternRefOperation
CFBasicHashAddValue
- retainCount
__CFDoExternRefOperation
CFBasicHashRemoveValue //CFBasicHashRemoveValue 为0时,-release调用dealloc
各个方法都通过同一个__CFDoExternRefOperation函数,调用一系列名称相似的函数。并且从函数名看出苹果采用散列表(引用计数表)来管理引用计数,表键值为内存块地址的散列值。然而GNUStep将引用计数保存在对象占用内存块头部的变量中(objc_layout这个结构体中)。
-
内存块头部管理引用计数的好处:
- 少量代码皆可完成
- 能够统一管理引用计数内存块与对象内存块。
-
引用技术表管理引用计数的好处:
1. 对象内存快的分配无需考虑内存块头部- 引用计数表各记录中存有内存块地址,可从各个记录追溯到各个内存块。
第二条特征在调试时很重要,即使出现故障导致对象占用的内存块损坏,但只要引用计数表没有被损坏,就能够确认各个内存块的地址
4.autorelease方法
-
NSAutoreleasePool是通过以AutoreleasePoolPage为结点的双向链表来实现的。AutoreleasePoolPage是一个C++实现的类,类结构如图:
- magic 用来校验 AutoreleasePoolPage 的结构是否完整。
- next 指向最新添加的 autoreleased 对象的下一个位置,初始化时指向 begin()。
- thread 指向当前线程。
- parent 指向父结点,第一个结点的 parent 值为 nil。
- child 指向子结点,最后一个结点的 child 值为 nil。
- depth 代表深度,从 0 开始,往后递增 1。
- hiwat 代表 high water mark。
- AutoreleasePoolPage每个对象会开辟4096字节内存(也就是虚拟内存一页的大小),除了实例变量所占空间,剩下的空间全部用来储存autorelease对象的地址内存结构如图:
-
在Cocoa框架中,NSRunloop每次循环过程中NSAutoreleasePool对象被生成或废弃。在大量产生autorelease对象时,只要不废弃NSAutoreleasePool那么生成的对象就不能被释放,在此情况下有时会产生内存不足的现象,因此有必要适当的生成,持有和废弃NSAutoreleasePool。通常在使用Objective-C,无论调用哪一个对象的autorelease/retain方法,实现上都是调用NSObject类的autorelease/retain实例方法,但是对于NSAutoreleasePool类,autorelease/retain实例方法已被重写,因此运行时会出错(exception)。autorelease实际上把对象的释放时机交给NSAutoreleasePool管理,使用方法如下:
-
生成并持有NSAutoreleasePool对象。
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // 等同于 objc_autoreleasePoolPush()
-
调用已分配对象的autorelease实例方法。
id obj = [[NSObject alloc] init]; [obj autorelease]; // 等同于 objc_autorelease()obj
-
废弃NSAutoreleasPool对象(自动调用分配对象的release)。
[pool drain]; // 等同于 objc_autoreleasePoolPop(pool)
-
5.ARC规则
ARC(Automatic Reference Counting)是编译阶段自动做了retain/release,原先需要手动添加处理引用计数的代码可以自动地由编译器完成,但实际上只有编译器是无法完全胜任的,在此基础上还需要Objective-C运行时库协助。同一程序中按文件单位可以选择ARC有效和无效。Core Foundation中的malloc()或者free()等,还是需要自己手动进行内存管理。ARC规则如下
- 不能使用retain/release/retainCount/autorelease。
- 不能使用NSAllocateObject/NSDeallocateObject。
- 必须遵守内存管理的方法命名规则。
- 不要显式调用dealloc。
- 使用@autoreleasepool块代替NSAutoreleasePool。
- 不能使用区域。
虽说ARC有效时,不能使用区域(NSZone)。不管ARC是否有效,区域在现在的运行时系统(编译器宏__OBJC2__)中已单纯的被忽略 -
对象型变量不能作为C语言结构体的成员。
struct Data { NSMutableArray *array; /* error: ARC forbids Objective-C objs in structs or unions NSMutableArray *array */ } /* 要把对象型白能量加入到结构体成员中时,可强制转换为void *(见下一条规则)或是附加“__unsafe_unreatained”修饰符。但是附有“__unsafe_unreatained”修饰的变量不属于编译器的内存管理对象,可能造成内存泄露或者崩溃。*/ struct Data { NSMutableArray __unsafe_unreatained *array; / }
-
显式转换“id” 和 “void”。
-
在MRC下"id"和"void *"可以强制转换,但在ARC下编译器报错,代码如下:
id obj = [[NSObject alloc] init]; void *p = obj; // ARC编译报错 id o = p; // ARC编译报错 [o release];
-
ARC情况下要用 "bridge转换",但是ARC不推荐此用法,这种转换经常用在Objective-C和Core Foundation对象之间的相互变换。
- __bridge转换:只做类型转换,在不改变对象的引用计数,可能会造成悬垂指针而导致程序崩溃。
-
__bridge_retained转换:可使目标变量也持有赋值对象。对象引用计数加1。以下是ARC和MRC等价的代码
// ARC id obj = [[NSObject alloc] init]; void *p = (__bridge_retained void *)obj; // 等效 MRC 代码 id obj = [NSObject alloc] init]; void *p = (void *)obj; [(id)p retain];
-
__bridge_transfer转换:被转换的变量所持有的对象在赋值给目标变量后随之释放。
// ARC ① (void)(__bridge_transfer id)p; //指定返回结果为void ,不利用返回值 对象引用计数减1 ② id obj = (__bridge_transfer id)p; // 如果返回值赋值给目标变量,转换后对象引用计数不变 // 等效 MRC 代码 ① [p release]; ② id obj = (id)p; [obj retain];[(id)p release];
-
6.所有权修饰符
-
__strong修饰符:对对象实例的强引用,是id类型或对象类型的默认所有权修饰符。持有强引用的变量在超出其作用域时被废弃,随着强引用的失效,引用的对象
会随之释放。// 以下两行代码是等效的 id obj = [[NSObject alloc] init]; id __strong obj = [[NSObject alloc] init];
- __weak修饰符:提供弱引用,弱引用不能持有对象实例,与__strong修饰符相反。可用来解决循环引用造成的内存泄露问与题。
- __unsafe_unretained修饰符:不安全的所有权修饰符,尽管ARC式的内存管理属于编译器的工作,但附有__unsafe_unretained修饰符的变量不属于编译器的内存管理对象,既不持有对象的强引用也不持有对象的弱引用,对象的可能随时被释放,访问释放的对象运行时会崩溃。
-
__autoreleasing修饰符:对象变量要为自动变量(包含局部变量、函数以及方法参数)。利用“__objc_autoreleasePoolPrint()”调试autoreleasepool上的对象
-
等价于MRC时调用对象的autorelease方法。以下两段代码等效:
// MRC NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; id obj = [[NSObject alloc] init]; [obj autorelease]; [pool drain]; // ARC @autoreleasepool { id __autoreleasing obj = [[NSObject alloc] init]; }
-
显式的附加__autoreleasing修饰符和显式度附加__strong修饰符一样“罕见”,有些情况下非
显式的声明__autoreleasing修饰符也是可以的,这些情况会自动将对象注册到自动释放池。
1.非自己生成并持有的对象@autoreleasepool { id obj = [NSMutableArray array]; // 变量obj为对象的强引用(默认__strong),编译器判断方法名后(非alloc/new等)自动注册大autoreleasepool }
2.id的指针和对象的指针(二级指针)在没有显示的指定时会被附加上__autoreleasing修饰符。
NSError *error = nil; //所有权为__strong类型 NSError **pError = &error; //编译出错, NSError * (对象)默认为_autoreleasing类型,赋值前后所有权不匹配。 NSError * __strong *pError = &error; //编译正确
3.自己生成并持有的对象作为返回值
+ (id)array { id obj = [[NSMutableArray alloc] init]; return obj; }
4.虽然__weak修饰符是为了避免循环引用而使用的,但访问附有__weak修饰符的变量时实j 际上必定要访问注册到autoreleasepool的对象为了避免该对象被废弃,注册到autoreleasepool之后可以保证在autoreleasepool块结束之前对象存在。
id __weak obj1 = obj0; NSLog(@"class = %@",[obj1 class]); // 以下代码和以上代码相同 id __weak obj1 = obj0; id __autoreleasing tmp = obj1; NSLog(@"class = %@",[tmp class]);
-
7.引用计数查看
Apple 提供一些方法查看对象的引用计数,但是并不能完全信任这些函数提供的引用计数值。对于已释放的对象一级不正确的对象地址,有时 也返回”1“,在多线程中,因为存在竞态条件的问题,所以取得的的数值不一定可信。
[object retainCount]; //得到object的引用计数,此方法仅仅适用于MRC
_objc_rootRetainCount(object); // ARC和MRC适用,头文件声明:OBJC_EXTERN int _objc_rootRetainCount(id);
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。