iOS 底层 day26 内存管理 引用计数器 weak指针
2020-09-24 本文已影响0人
望穿秋水小作坊
一、引用计数器
1. 引用计数器(retainCounter)保存在哪里
- 在
64bit中,引用计数器可以直接存储在优化过的isa指针中
isa 位域详情
- 当
isa中的位域不够存储引用计数器时,存储在SideTable类中
SideTable的结构
二、__weak 指针
1. __weak 、__strong、__unsafe_unretained 有什么区别?
-
__strong:会对对象进行强引用,会使对象的引用计数器+1 -
__weak:不会改变对象的引用计数器,对象被释放时会被置为 nil,比较安全 -
__unsafe_unretained:不会改变对象的引用计数器,对象被释放就会造成野指针,不安全,可能导致崩溃
2. __weak 、__strong、__unsafe_unretained 可以从代码的角度感受他们的不一样(打开 Zombie Objects);
#import "Person.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
__strong Person *person1;
__weak Person *person2;
__unsafe_unretained Person *person3;
NSLog(@"begin");
{
Person *person = [[Person alloc] init];
person1 = person;
// person2 = person;
// person3 = person;
}
NSLog(@"%@",person1);
// NSLog(@"%@",person2);
// NSLog(@"%@",person3);
NSLog(@"end");
}
return 0;
}
3. __weak 指针在什么时候被置为nil?
-
我们从 objc4 源码中寻找答案,轨迹如下:① delloc ② objc_rootDealloc ③ rootDealloc ④ object_dispose ⑤ objc_destructInstance ⑥ clearDeallocating ⑦ clearDeallocating_slow ⑧weak_clear_no_lock
rootDealloc
weak_clear_no_lock
- 我们从源码中得出:
- 当对象即将被销毁时,会调用对象的
dealloc方法,在dealloc方法内部,会找到该对象在SideTable中的弱引用表,将它们置空,然后从SideTable中删除。
三、autorelease 自动释放池
1. 我们经常可以在 main 函数中看到 @autoreleasepool,这明显是一个编译器的语法糖,思考碰到这种语法糖,我们如何让它原形毕露?
- 我们可以通过指令将其转换成 C++代码,这样我们就能看到其本质代码了。
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
转换后的 main 代码
- 我们可以看到
@autoreleasepool实际上被转换成了__AtAutoreleasePool __autoreleasepool;,这句代码是作用是声明了一个结构体,那么__AtAutoreleasePool这个结构体有什么内容呢?
__AtAutoreleasePool 结构体
- 所以根据这个结构体的特性(需要一点点 C++知识),上述代码可以被我们这样理解:
main 函数的实际代码效果
2. objc_autoreleasePoolPush() 和 objc_autoreleasePoolPop(atautoreleasepoolobj) 这两个函数干了什么?
- 这两个函数的从我们编译后的 cpp 文件中只有声明,找不到实现,我们尝试从
objc4源码中寻找他们的踪影。 - 在
NSObject.mm我们发现了这两个函数的踪影 - 我们发现这两个函数内部涉及到一个很频繁的对象
AutoreleasePoolPage,我需要理解它
3. AutoreleasePoolPage 的理解
AutoreleasePoolPage结构体的内部成员变量
- 从
AutoreleasePoolPage * const parent;和AutoreleasePoolPage *child;这两个成员变量,我们可以大概得出,这个是一个双向链表。
4. AutoreleasePoolPage 的图解
-
autorelease 对象: 是在 MRC 模式下,如下创建的对象:Person *person = [[[Person alloc] init] autorelease]; - 从
AutoreleasePoolPage的new方法中看到,每个AutoreleasePoolPage对象占用4096个字节内存,除了用来存放它内部的成员变量,剩下的空间来存放autorelease 对象的地址 - 所有的
AutoreleasePoolPage对象通过双向链表的形式连接在一起 - 一个
AutoreleasePoolPage大概能存储 500个对象指针,当AutoreleasePoolPage放满之后,会创建新的AutoreleasePoolPage,并将 child 指针指向新的AutoreleasePoolPage;将新的AutoreleasePoolPageparent 指针指向源AutoreleasePoolPage;这样就形成了双向链表
AutoreleasePoolPage双向列表图解
5. 思考 MRC 环境下,下列代码在 AutoreleasePoolPage的存储情况
示例代码
- 存储结构情况
存储结构情况
-
POOL_BOUNDARY是标记位,本质上的值就是0,它用于标记每个自动释放池的开始位置 -
我们还可以通过声明一个 Foundation 内部函数 :
extern void _objc_autoreleasePoolPrint(void);来从 Xcode 日志中查看自动释放池情况。 -
我们在
Person *p4 = [[[Person alloc] init] autorelease];后面加上代码_objc_autoreleasePoolPrint(); -
获得如下打印,印证我们上图的想法是正确的
objc[7180]: ##############
objc[7180]: AUTORELEASE POOLS for thread 0x1000d3dc0
objc[7180]: 7 releases pending.
objc[7180]: [0x10280e000] ................ PAGE (hot) (cold)
objc[7180]: [0x10280e038] ################ POOL 0x10280e038
objc[7180]: [0x10280e040] 0x100637410 Person
objc[7180]: [0x10280e048] 0x100637470 Person
objc[7180]: [0x10280e050] ################ POOL 0x10280e050
objc[7180]: [0x10280e058] 0x100637970 Person
objc[7180]: [0x10280e060] ################ POOL 0x10280e060
objc[7180]: [0x10280e068] 0x100637990 Person
objc[7180]: ##############
5. 请问MRC 环境下,下面的person 什么时候释放?
- (void)viewDidLoad {
[super viewDidLoad];
@autoreleasepool {
YYPerson *person = [[[YYPerson alloc] init] autorelease];
}
NSLog(@"%s", __func__);
}
-
@autoreleasepool大括号结束时马上释放,这就是我们前面学的知识
6. 请问MRC 环境下,下面的person 什么时候释放?
- (void)viewDidLoad {
[super viewDidLoad];
YYPerson *person = [[[YYPerson alloc] init] autorelease];
NSLog(@"%s", __func__);
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
NSLog(@"%s", __func__);
}
- (void)viewDidAppear:(BOOL)animated {
[super viewWillAppear:animated];
NSLog(@"%s", __func__);
}
- 我们从打印结果来看
person会在viewWillAppear结束后才被释放 - 这是由于 RunLoop 造成的,每次 RunLoop一次循环任务结束,准备进入休眠的时候才会去释放该释放的对象。(
感觉这点研究不够通彻)
四、ARC为我们做了什么?
1. ARC 为我们自动添加 release
main(){
id a;
}
- 在
__Strong类型的变量的作用域结束时,自动添加 release函数
2. ARC 为我们自动添加 retain
main(){
id a;
__strong id b = a;
}
- 在
__Strong id b = a;会自动对 a 进行 retain;然后结尾处自动添加两次 release函数
3. 在 ARC 的环境下,下面 person 在什么时候释放?
- (void)viewDidLoad {
[super viewDidLoad];
YYPerson *person = [[YYPerson alloc] init] ;
_objc_autoreleasePoolPrint();
NSLog(@"%s", __func__);
}
- 我们从
_objc_autoreleasePoolPrint();打印日志来看person并没有加入到自动释放池中 - person 在
NSLog(@"%s", __func__);之后被释放了 - 其实在 ARC 环境下,上述代码编译器会给我们在大括号结束处加上
[person release]这样的代码,所以才会有这种结果