一、ARC
1. autorelease
使用 NSMutableArray 类的 array 方法等可以取得谁都不持有的对象,这些方法都是通过 autorelease 而实现的。
2. GNUstep
GNUstep 是 Cocoa 框架的互换框架,可看到源码,参考 ARC 的实现方式。
简单来说就是通过一个 obj_layout 的结构体中的 retained 来保存了引用计数:
- 在 Objective-C 的对象中存有引用计数这一整数值;
- 调用 alloc 或者是 retain 方法后,引用计数加一;
- 调用 release 之后,引用计数减一;
- 引用计数数值为0时,调用 dealloc 方法废弃对象。
3. 引用计数两种保存方式对比
GNUstep将引用计数保存在对象占用内存块头部的变量中,而苹果的实现则是保存在引用计数表的记录中,两者各有优点。
通过内存块头部管理的好处是:
- 少量代码即可完成;
- 能够统一管理引用计数的内存块和对象的内存块。
通过引用计数表来管理的好处是:
- 对象用的内存块分配不需要考虑内存块头部;
- 引用计数表各记录中存有内存块地址,可从各个记录追溯到各对象的内存块;
- 利用工具检测内存泄露时,引用计数表的各记录也有助于检测各对象的持有者是否存在。
4. NSRunLoop 每次循环过程中 NSAutoreleasePool 对象被生成或者是废弃。
5. ARC 所有权修饰符有 4 种:
__strong(默认)
__weak
__unsafe_unretained
__autoreleasing
6. 所谓内存泄漏就是应当废弃的对象在超出其生存周期后继续存在。
7. ARC 有效情况下的编码规则:
- 不能使用retain/release/retainCount/autorelease
- 不能使用NSAllocateObject/NSDeallocateObject
- 必须遵守内存管理的方法命名规则
- 不要显式调用dealloc
- 使用@autoreleasepool块替代NSAutoreleasePool
- 不能使用区域NSZone
- 对象型变量不能作为c语言结构体的成员
- 显式转换id和void*
8. 属性声明的关键字与所有权修饰符的对应关系:
- assign: __unsafe_unretained
- copy: __strong(但是赋值的是被复制的对象)
- retain: __strong
- strong: __strong
- unsafe_unretained: __unsafe_unretained
- weak: __weak
9. ARC 的实现
ARC 是由编译器进行内存管理的,但实际上只有编译器是无法完全胜任的,在此基础上还需要 Objective-C 运行时库的协助:
- clang (LLVM 编译器)3.0以上
- objc4 Objective-C 运行时库493.9以上
10. 关于 __weak 修饰符的实现
若附有__weak修饰符的变量所引用的对象被抛弃,则将nil赋值给该变量。
若用附有__weak修饰符的变量,即是试用注册到autoreleasepool中的对象。
将 __strong 修饰的 obj 赋值给 __weak 的 obj1 将会发生什么呢?
eg:id __weak obj1 = obj;
下面👇是编译器的模拟代码:
id obj1;
objc_initWeak(&obj1, obj);//初始化 obj1
objc_destroyWeak(&obj1);//释放 obj1
1.其中 objc_initWeak 函数又会调用 objc_storeWeak 函数
objc_initWeak(&obj1, obj)等同于:
obj1 = 0;
objc_storeWeak(&obj1, obj);
2.objc_destroyWeak 函数会将 0 作为参数调用 objc_storeWeak 函数
objc_destroyWeak(&obj1)等同于:
objc_storeWeak(&obj1, 0);
完整的模拟代码如下:
id obj1;
obj1 = 0;
objc_storeWeak(&obj1, obj);
objc_storeWeak(&obj1, 0);
//这里第一个storeWeak函数把第二个参数的赋值对象的地址作为键值,将第一个参数的附有__weak修饰符的变量的地址注册到weak表中。
//可以注意到,第二次调用的时候第二个参数为0, 也就是说第二个参数为-0的时候,会把变量的地址从weak表中删除
weak表是什么呢? 此时联想到引用计数表,他们都是利用散列来实现的。 这里的一个键值可以注册多个变量的地址。 就跟一个对象可以同时付给多个附有__weak修饰符的变量。也就是说,如果你用一个废弃对象的地址作为键值来检索,你能够告诉的获取对应的附有__weak修饰符的变量的地址。
当释放对象的时候,废弃掉谁都不持有的对象,程序后续还会出现动作:
1、objc_release
2、因为引用计数为0, 所以执行dealloc
3、_objc_rootDealloc
4、object_dispose
5、objc_destructInstance
6、objc_clear_deallocating
最后调用的 objc_clear_deallocating 函数会出现如下动作:
1、从weak表中获取废弃对象的地址为键值的记录。
2、将包含在记录中的所有附有__weak修饰符变量的地址,赋值为nil。
3、从weak表中删除该记录。
4、从引用计数表中删除废弃对象的地址为键值的记录。
使用附有 __weak 修饰符的变量,即是使用注册到 autoreleasepool 中的对象。
id __weak obj1 = obj 可以转换为如下形式:
id obj1;
objc_initWeak(&obj1, obj);
id tmp = objc_loadWeakRetained(&obj1);
objc_autorelease(tmp);
objc_destroyWeak(&obj1);
这里增加了objc_loadWeakRetained和objc_autorelease的调用。
1、objc_loadWeakRetained:函数取出附有__weak修饰符变量所引用的对象,并retain。
2、objc_autorelease函数将对象注册到autoreleasepool中。
最后提醒的是:id __weak obj = [[NSObject alloc] init];
和 id __unsafe_unretained obj = [[NSObject alloc] init];
这样是不可以的,前者是因为不能持有对象,后者是obj被赋予的是 悬垂指针。
虽然在arc中不会造成内存泄露,但是还是不要这样使用的好。
11. 引用计数
- 获取引用计数值的函数:uintptr_t_objc_rootRetainCount(id obj) , 这个函数可以获取指定对象的引用计数值(ARC中,retainCount已经不能用了),但是也不能完全信任该函数取得的值,对于已经释放的对象以及不正确的对象地址,有时也会返回 1;
- 函数 _objc_autoreleasePoolPrint函数会观察注册到autoreleasepool中的引用对象。
二、 Blocks
1. Blocks 是 C 语言的扩充功能。
一句话概括 Blocks 的扩充功能:带有自动变量(局部变量)的匿名函数。匿名函数就是不带有名称的函数。
2. C 语言的函数中可能使用的变量:
自动变量(局部变量)
函数的参数
静态变量(静态局部变量)
静态全局变量
全局变量
3. 截获自动变量
- 只针对Block中使用的自动变量
- 自动变量的值以成员变量的形式被保存到Block的结构体实例(或者说被其持有),通过__cself被使用;如果是__block变量,则转化成结构体,其指针作为成员变量保存到Block结构体中
4. Block 与 __block 变量的实质
- Block:栈上 Block 的结构体实例
- __block:栈上 __block 变量的结构体实例
Block 类及其对应的存储域如下:
Block 类及其对应的存储域.jpeg
Block 的副本
Block 的副本.jpeg
Block 从栈复制到堆时对 __block 变量产生的影响
Block 从栈复制到堆时对 __block 变量产生的影响.jpeg
复制 __block 变量
复制 __block 变量.jpeg什么时候栈上的Block会被复制到堆上呢?
- 调用Block的copy实例方法时
- Block作为函数返回值返回时
- 将Block赋值给附有__strong修饰符id类型的类或Block类型成员变量时
- 在方法名中含有usingBlock的Cocoa框架方法或GCD的API中传递Block时
5. 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操作
GCD
Grand Central Dispatch,是 iOS 目前最常用的多线程处理技术,在此之前一般使用 NSObject 类的 performSelector 系列方法或者 NSTherd 相关方法实现多线程。
1. 使用多线程的弊端
- 多个线程更新相同的资源会导致数据的不一致(数据竞争)
- 停止等待事件的线程会导致多个线程相互持续等待(死锁)
- 使用太多线程会消耗大量内存
2. GCD 相关 API
GCD 的 API3. Dispatch Queue 的实现依托于:
- 用于管理追加的 Block 的 C 语音层实现的 FIFO 队列;
- Atomic 函数中实现的用于排他控制的轻量级信号
用于管理线程的 C 语音层实现的一些容器
除此之外,GCD 是依托于系统内核级的实现,如下图:
用于实现 Dispatch Queue 而使用的软件组件
4. Dispatch Source
它是BSD系内核惯有功能kqueue的包装。kqueue是在XUN内核中发生各种事件时,在应用程序编程方执行处理的技术。
其CPU负荷非常小,尽量不占用资源。kqueue可以说是应用程序处理XUN内核中发生的各种事件的方法中最优秀的一种。
Dispatch Source 的种类入下图:
Dispatch Source 的种类
网友评论