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
- 我们从源码中得出:
- 当对象即将被销毁时,会调用对象的
dealloc
方法,在dealloc
方法内部,会找到该对象在SideTable
中的弱引用表,将它们置空,然后从SideTable
中删除。
三、autorelease 自动释放池
1. 我们经常可以在 main 函数中看到 @autoreleasepool
,这明显是一个编译器的语法糖,思考碰到这种语法糖,我们如何让它原形毕露?
- 我们可以通过指令将其转换成 C++代码,这样我们就能看到其本质代码了。
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
- 我们可以看到
@autoreleasepool
实际上被转换成了__AtAutoreleasePool __autoreleasepool;
,这句代码是作用是声明了一个结构体,那么__AtAutoreleasePool
这个结构体有什么内容呢?
- 所以根据这个结构体的特性(需要一点点 C++知识),上述代码可以被我们这样理解:
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
;将新的AutoreleasePoolPage
parent 指针指向源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]
这样的代码,所以才会有这种结果