工具:利用clang(LLVM编译器)的命令:clang -rewrite-objc 源代码文件名
将OC转换成对应的C++源代码。
另类总结:
四类关键字(
alloc/new/copy/mutableCopy等
| retain
| release
| dealloc
)四种所有权修饰符(
__strong
| __weak
| __unsafe_unretained
| __autoreleasing
)两张散列表(引用计数表和weak表)+ 一个动态数组(
autoreleasepool
)+NSRunLoop属性(
assign
| copy
| retain
| strong
| unsafe_unretained
| weak
)Toll-Free Bridge - 传送门
思路串联:
MRC下,内存需要人工管理,通过alloc等四类关键字(本质:calloc、free)结合引用计数表进行,
1. 自动引用计数
在LLVM【编译器】中设置ARC为有效状态,就无需键入retain或release代码了,编译器将结合【OC运行时】基于引用计数自动进行内存管理。
引用计数/内存管理
对照明设备所做的工作 | 对OC对象所做的动作 |
---|---|
开灯 | 生成对象 |
需要照明 | 持有 |
不需要照明 | 释放 |
关灯 | 废弃 |
内存管理的思考方式 | 对应OC方法 |
---|---|
自己生成的对象,自己所持有 | alloc/new/copy/mutableCopy等 |
非自己生成的对象(比如[NSArray array]),自己也能持有 | retain |
1. 不再需要自己持有的对象时释放<br />2. 无妨释放非自己持有的对象(比如多次release) | release |
当对象不被任何其他对象持有时废弃 | dealloc |
苹果的实现
=> alloc/retain/retainCount/release/dealloc实现
alloc => 调用class_createInstance
(calloc)分配内存 => 设置isa指针和成员变量初始值(0) => 在引用计数表中添加纪录,并将引用计数值置为1
case OPERATION_retain:
CFBasicHashAddValue( table, obj );
return obj;
case OPERATION_retainCount:
count = CFBasicHashGetCountOfKey( table, obj );
return count;
case OPERATION_release:
count = CFBasicHashRemoveValue( table, obj );
return 0 == count;
dealloc => 删除引用计数表中的对应记录 => free内存块
=> autorelease实现
autorelease方法的IMP Caching
注意:无论调用哪一个对象的autorelease实例方法,实际上调用的都是NSObject类的autorelease实例方法。(NSAutoreleasePool类的autorelease实例方法被重载了,运行时会报错!!!)
ARC - 只是自动地帮助我们处理“引用计数”的相关部分。
文件的编译属性设置:-fobjc-arc
或 -fno-objc-arc
所有权修饰符
@autoreleasepool{}块替代了NSAutoreleasePool类对象的生成持有和废弃
__autoreleasing修饰符替代了autorelease方法的调用
autoreleasepool自动注册
不以alloc/new/copy/mutableCopy开头的方法(init系列方法除外)返回的对象将自动注册
id的指针或对象的指针在没有显示指定时会被附加上__autoreleasing修饰符。
对象指针型赋值时,所有权修饰符必须一致。
id __autoreleasing *obj;
NSObject * __autoreleasing *obj;
拓展:附有__strong/__weak修饰符的变量类似于C++中的智能指针std::shared_ptr和std::weak_ptr。
ARC规则
- ……
- 须遵守内存管理方法的命名规则
- 以alloc/new/copy/mutableCopy名称开头的方法必须返回给调用方所应当持有的对象
- 以init开始的方法必须是返回类型为id/class/superclass/subclass的实例方法
- ……
- 显示转换id和void *
- CF对象与OC对象的转换不需要使用额外的CPU资源,所以被称为Toll-Free Bridge
// ARC: void *p = (__bridge_retained void *)obj;
CFTypeRef CFBridgeRetain(id X) {
return (__bridge_retained CFTypeRef)X;
}
// MRC
id obj = [[NSObject alloc] init];
void *p = obj;
[(id)p retain];
// ARC: id obj = (__bridge_transfer id)p;
id CFBridgeRelease(CFTypeRef X) {
return (__bridge_transfer id)X;
}
// MRC
id obj = (id)p;
[obj retain];
[(id)p release];
- CF还有以下方法:
CFRetain
、CFRelease
、CFGetRetainCount
和CFShow
属性
属性的特性修饰符必须和对应成员变量的所有权修饰符一致!!!
// weak和默认的__strong冲突了!!!
@property (nonatomic, weak) id obj;
数组
???必须将nil赋值给所有数组元素,使得元素所赋值对象的强引用失效,从而释放那些对象;然后再使用free函数废弃内存块,否则会有内存泄漏!!!
ARC实现
__strong
赋值分为两种情况:alloc/new/copy/mutableCopy系列和其他
涉及的函数有:objc_msgSend
、objc_release
和objc_retain
、objc_autorelease
__weak
修饰符功能:
- 若附有__weak修饰符的变量所引用的对象被废弃,则将nil赋值给该变量;
- 对象废弃时最后调用的objc_clear_deallocating函数的动作如下
1)从weak表中获取废弃对象的地址作为键值得记录;
2)将包含在记录中的所有附有__weak修饰符变量的地址,赋值为nil;
3)从weak表删除该记录;
4)从引用计数表中删除废弃对象的地址为键值的记录。
使用附有__weak修饰符的变量,即是使用注册到autoreleasepool中的对象;
- 源代码解读
1) objc_loadWeakRetained函数取出附有__weak修饰符的变量所引用的对象并retain;
2) objc_autorelease函数将对象注册到autoreleasepool中。
- 最佳实践:使用附有__weak修饰符的变量时,最好先暂时赋值给附有__strong修饰符的变量后再使用;从而避免对象多次注册到autoreleasepool中。
不能使用__weak修饰符的情况
- iOS4及以下
- 通过
NS_AUTOMATED_REFCOUNT_WEAK_UNAVAILABLE
声明了不支持的类,比如NSMachPort
- 以下方法返回NO的时候:
- (BOOL)allowsWeakReference;
- (BOOL)retainWeakReference;
__autoreleasing修饰符
等同于ARC无效时调用对象的autorelease方法,即 objc_autorelease
方法的调用。
2. Blocks
-
语法:完整形式(
^T (…) { … }
)=> 基于推断省略返回类型(^ (…) { … }
)=> 省略参数(^ { … }
)
很像函数指针, - 变量使用:使用typedef提高可读性
- 截获自动变量
- 赋值导致编译错误(Mutable类的add方法不会!) => 解决方案:使用
__block
说明符 - 不能截获C语言数组 => 解决方案:使用指针
实现
- 本质 OC对象(结构体)
-
isa 类结构指针
和 三大类型 _NSConcrete[Stack | Malloc | Global
]Block FuncPtr 函数指针
-
Desc
、Flags
和其他
-
- 截获自动变量 - 只针对Block中使用的自动变量
-
__cself
和 OC中的self
、C++中的this
- 自动变量的值以成员变量的形式被保存到Block的结构体实例(或者说被其持有),通过
__cself
被使用;如果是__block变量,则转化成结构体,其指针作为成员变量保存到Block结构体中 - 在Block中修改自动变量的两种方法:
- 静态变量、静态全局变量或全局变量
-
__block
存储域类说明符 - 类似于static、auto和register说明符,指定将变量值设置到哪个存储域中。
-
三种类型
- _NSConcreteGlobalBlock - 存储域:程序的数据区域
通过以下情况得到实例
1.记述全局变量的地方有Block语法时
2.Block语法的表达式中不使用截获的自动变量时 - _NSConcreteStackBlock - 存储域:栈;复制效果:到堆
除Global之外的Block语法生成的都是栈Block - _NSConcreteMallocBlock - 存储域:堆;复制效果:引用计数增加
实际上当ARC有效时,多数情况编译器会恰当地判断,自动生成将Block从栈上复制到堆上的代码!
什么时候栈上的Block会被复制到堆上呢?
1. 调用Block的copy实例方法时
2. Block作为函数返回值返回时
3. 将Block赋值给附有__strong修饰符id类型的类或Block类型成员变量时
4. 在方法名中含有usingBlock的Cocoa框架方法或GCD的API中传递Block时
需要手动复制的情形:NSArray的initWithObjects:(除4外作为方法参数时;注释:现在应该连这个也OK了,请测试!!!);也即是ARC万能。
实质/本质
-
Block
- 栈/堆/数据区上的 Block 的结构体实例,isa指针 -
__block
变量 - 栈上 __block 变量的结构体实例
Block超出变量作用域可存在的理由 => 将Block和__block变量从栈上复制到堆上解决
__block变量的结构体成员变量__fowarding存在的理由 => 实现无论__block变量配置在栈上还是堆上都能正确地进行访问
Block循环引用
原因:Block中附有__strong修饰符的对象类型自动变量在从栈复制到堆上时,该对象会被Block所持有。
解决方案:
- ARC:通过
__weak
或__unsafe_unretained
修饰符(iOS4)来替代__strong
类型的被截获的自动变量
通过__block
说明符和设置nil来打破循环 - MRC:通过
__block
说明符指定变量不被Block所retain;ARC下__block说明符的作用仅限于使其能在Block中被赋值。
"原理"
如果对block做一次copy操作, block的内存就会在堆中
* 它会对所引用的对象做一次retain操作
* 非ARC : 如果所引用的对象用了__block修饰, 就不会做retain操作
* ARC : 如果所引用的对象用了__unsafe_unretained\__weak修饰, 就不会做retain操作
3. Grand Central Dispatch(GCD)
GCD API
获取系统提供的队列:Main/Global Dispatch Queue;无需内存管理
使用 Concurrent Dispatch Queue 和 dispatch_barrier_async 函数可实现高效率的数据库访问和文件访问。
GCD实现
GCD分为Dispatch Queue和Dispatch Source两个部分,各自的实现如下:
Dispatch Queue
GCD是XNU内核级所实现的多线程管理API,根据CPU核等系统软硬件情况进行了优化的线程池,提供高性能的简单编程接口。
(注释:Darwin - NeXT电脑公司开发的用于NEXTSTEP的XNU内核是兼有Mach3微内核和大量来自BSD宏内核的元素(进程、网络、虚拟文件系统)以及I/O Kit的混合内核)
Dispatch Source
实现:BSD系内核惯有功能kqueue的包装(XNU内核事件发生时,能在应用程序编程方执行处理)。
"使用惯例"
1. create or get - 获取队列
2. dispatch_source_create - 基于“监听”的内核事件在队列上构建Dispatch Source
3. dispatch_source_set_( timer|event_handler|cancel_handler ) - 配置Dispatch Source
一些列的处理方法,比如:dispatch_source_get_data、dispatch_source_cancel、dispatch_source_release
4. dispatch_resume(source) - 启动事件源监听