iOS底层原理 内存管理 那些你不知道的原理汇总 --(12)

看完本文章你将了解到

  1. DisplayLink和timer的使用和原理
  2. 内存分配和内存管理
  3. 自动释放池原理
  4. weak指针原理和释放时机
  5. 引用计数原理

/

CADisplayLink是将任务添加到runloop中,loop每次循环便会调用targetselector,使用这个也能监测卡顿问题。首先介绍下API

1
2
3
4
5
6
7
+ (CADisplayLink *)displayLinkWithTarget:(id)target selector:(SEL)sel;
//runloop没循环一圈都会调用
- (void)addToRunLoop:(NSRunLoop *)runloop forMode:(NSRunLoopMode)mode;
//从runloop中删除
- (void)removeFromRunLoop:(NSRunLoop *)runloop forMode:(NSRunLoopMode)mode;
//取消
- (void)invalidate;

我们在一个需要pushVC中运行来观察声明周期

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@property (nonatomic,strong) CADisplayLink *link;

//初始化
self.link = [FYDisplayLink displayLinkWithTarget:self selector:@selector(test)];
[self.link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC, 1 * NSEC_PER_SEC);
dispatch_source_set_event_handler(timer, ^{
@synchronized (self) {
NSLog(@"FPS:%d",fps);
fps = 0;
}
});
dispatch_resume(timer);
//全局变量
dispatch_source_t timer;
static int fps;

- (void)test{

@synchronized (self) {
fps += 1;
}
}
- (void)dealloc{
[self.link invalidate];
NSLog(@"%s",__func__);
}
//log
2019-07-30 17:44:37.217781+0800 day17-定时器[29637:6504821] FPS:60
2019-07-30 17:44:38.212477+0800 day17-定时器[29637:6504821] FPS:60
2019-07-30 17:44:39.706000+0800 day17-定时器[29637:6504821] FPS:89
2019-07-30 17:44:40.706064+0800 day17-定时器[29637:6504821] FPS:60
2019-07-30 17:44:41.705589+0800 day17-定时器[29637:6504821] FPS:60
2019-07-30 17:44:42.706268+0800 day17-定时器[29637:6504821] FPS:60
2019-07-30 17:44:43.705942+0800 day17-定时器[29637:6504821] FPS:60
2019-07-30 17:44:44.705792+0800 day17-定时器[29637:6504821] FPS:60

初始化之后,对fps使用了简单版本的读写锁,可以看到fps基本稳定在60左右,点击按钮返回之后,linkVC并没有正常销毁。我们分析一下,VC(self)->link->target(self),导致了死循环,释放的时候,无法释放selflink,那么我们改动一下link->target(self)中的强引用,改成弱引用,代码改成下面的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@interface FYTimerTarget : NSObject
@property (nonatomic,weak) id target;
@end

@implementation FYTimerTarget
-(id)forwardingTargetForSelector:(SEL)aSelector{
return self.target;
}
- (void)dealloc{
NSLog(@"%s",__func__);
}
@end


FYProxy *proxy=[FYProxy proxyWithTarget:self];
self.link = [FYDisplayLink displayLinkWithTarget:self selector:@selector(test)];
[self.link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];

- (void)test{
NSLog(@"%s",__func__);
}

//log
2019-07-30 17:59:04.339934 -[ViewController test]
2019-07-30 17:59:04.356292 -[ViewController test]
2019-07-30 17:59:04.371428 -[FYTimerTarget dealloc]
2019-07-30 17:59:04.371634 -[ViewController dealloc]

FYTimerTargettarget进行了弱引用,selfFYTimerTarget进行强引用,在销毁了的时候,先释放self,然后检查selfFYTimerTarget,FYTimerTarget只有一个参数weak属性,可以直接释放,释放完FYTimerTarget,然后释放self(VC),最终可以正常。

NSTimer

使用NSTimer的时候,timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo会对aTarget进行强引用,所以我们对这个aTarget进行一个简单的封装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@interface FYProxy : NSProxy
@property (nonatomic,weak) id target;

+(instancetype)proxyWithTarget:(id)target;
@end
@implementation FYProxy
- (void)dealloc{
NSLog(@"%s",__func__);
}
+ (instancetype)proxyWithTarget:(id)target{
FYProxy *obj=[FYProxy alloc];
obj.target = target;
return obj;
}
//转发
- (void)forwardInvocation:(NSInvocation *)invocation{
[invocation invokeWithTarget:self.target];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel{
return [self.target methodSignatureForSelector:sel];
}
@end

FYProxy是继承NSProxy,而NSProxy不是继承NSObject的,而是另外一种基类,不会走objc_msgSend()的三大步骤,当找不到函数的时候直接执行- (void)forwardInvocation:(NSInvocation *)invocation,和- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel直接进入消息转发阶段。或者将继承关系改成FYTimerTarget : NSObject,这样子target找不到的函数还是会走消息转发的三大步骤,我们再FYTimerTarget添加消息动态解析

1
2
3
-(id)forwardingTargetForSelector:(SEL)aSelector{
return self.target;
}

这样子targetaSelector转发给了self.target处理,成功弱引用了self和函数的转发处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
FYTimerTarget *obj =[FYTimerTarget new];
obj.target = self;

self.timer = [NSTimer timerWithTimeInterval:1.0f
target:obj
selector:@selector(test)
userInfo:nil
repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
[self.timer setFireDate:[NSDate distantPast]];

//log
2019-07-30 18:03:08.723433+0800 day17-定时器[30877:6556631] -[ViewController test]
2019-07-30 18:03:09.722611+0800 day17-定时器[30877:6556631] -[ViewController test]
2019-07-30 18:03:09.847540+0800 day17-定时器[30877:6556631] -[FYTimerTarget dealloc]
2019-07-30 18:03:09.847677+0800 day17-定时器[30877:6556631] -[ViewController dealloc]

或者使用timerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block,然后外部使用__weak self调用函数,也不会产生循环引用。
使用block的情况,释放正常。

1
2
3
4
5
6
7
8
self.timer=[NSTimer timerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"123");
}];

//log
2019-07-30 18:08:24.678789+0800 day17-定时器[31126:6566530] 123
2019-07-30 18:08:25.659127+0800 day17-定时器[31126:6566530] 123
2019-07-30 18:08:26.107643+0800 day17-定时器[31126:6566530] -[ViewController dealloc]

由于linktimer是添加到runloop中使用的,每次一个循环则访问timer或者link,然后执行对应的函数,在时间上有相对少许误差的,每此循环,要刷新UI(在主线程),要执行其他函数,要处理系统端口事件,要处理其他的计算。。。总的来说,误差还是有的。

GCD中timer

GCD中的dispatch_source_t的定时器是基于内核的,时间误差相对较少。

1
2
3
4
5
6
7
8
9
10
//timer 需要强引用 或者设置成全局变量
timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC, 1 * NSEC_PER_SEC);
//设置
dispatch_source_set_event_handler(timer, ^{
//code 定时器执行的代码

});
//开始定时器
dispatch_resume(timer);

或者使用函数dispatch_source_set_event_handler_f(timer, function_t);

1
2
3
4
dispatch_source_set_event_handler_f(timer, function_t);
void function_t(void * p){
//code here
}

业务经常使用定时器的话,还是封装一个简单的功能比较好,封装首先从需求开始分析,我们使用定时器常用的参数都哪些?需要哪些功能?

首先需要开始的时间,然后执行的频率,执行的任务(函数或block),是否重复执行,这些都是需要的。
先定义一个函数

1
2
3
4
5
6
7
8
9
10
11
12
13
+ (NSString *)exeTask:(dispatch_block_t)block
start:(NSTimeInterval)time
interval:(NSTimeInterval)interval
repeat:(BOOL)repeat
async:(BOOL)async;
+ (NSString *)exeTask:(id)target
sel:(SEL)aciton
start:(NSTimeInterval)time
interval:(NSTimeInterval)interval
repeat:(BOOL)repeat
async:(BOOL)async;
//取消
+ (void)exeCancelTask:(NSString *)key;

然后将刚才写的拿过来,增加了一些判断。有任务的时候才会执行,否则直接返回nil,当循环的时候,需要间隔大于0,否则返回,同步或异步,就或者主队列或者异步队列,然后用生成的key,timervalue存储到全局变量中,在取消的时候直接用key取出timer取消,这里使用了信号量,限制单线程操作。在存储和取出(取消timer)的时候进行限制,提高其他代码执行的效率。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
+ (NSString *)exeTask:(dispatch_block_t)block start:(NSTimeInterval)time interval:(NSTimeInterval)interval repeat:(BOOL)repeat async:(BOOL)async{
if (block == nil) {
return nil;
}
if (repeat && interval <= 0) {
return nil;
}

NSString *name =[NSString stringWithFormat:@"%d",i];
//主队列
dispatch_queue_t queue = dispatch_get_main_queue();
if (async) {
queue = dispatch_queue_create("async.com", DISPATCH_QUEUE_CONCURRENT);
}
//创建定时器
dispatch_source_t _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
//设置启动时间
dispatch_source_set_timer(_timer,
dispatch_time(DISPATCH_TIME_NOW, time*NSEC_PER_SEC), interval*NSEC_PER_SEC, 0);
//设定回调
dispatch_source_set_event_handler(_timer, ^{
block();
if (repeat == NO) {
dispatch_source_cancel(_timer);
}
});
//启动定时器
dispatch_resume(_timer);
//存放到字典
if (name.length && _timer) {
dispatch_semaphore_wait(samephore, DISPATCH_TIME_FOREVER);
timers[name] = _timer;
dispatch_semaphore_signal(samephore);
}
return name;
}



+ (NSString *)exeTask:(id)target
sel:(SEL)aciton
start:(NSTimeInterval)time
interval:(NSTimeInterval)interval
repeat:(BOOL)repeat
async:(BOOL)async{
if (target == nil || aciton == NULL) {
return nil;
}
if (repeat && interval <= 0) {
return nil;
}

NSString *name =[NSString stringWithFormat:@"%d",i];
//主队列
dispatch_queue_t queue = dispatch_get_main_queue();
if (async) {
queue = dispatch_queue_create("async.com", DISPATCH_QUEUE_CONCURRENT);
}
//创建定时器
dispatch_source_t _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
//设置启动时间
dispatch_source_set_timer(_timer,
dispatch_time(DISPATCH_TIME_NOW, time*NSEC_PER_SEC), interval*NSEC_PER_SEC, 0);
//设定回调
dispatch_source_set_event_handler(_timer, ^{
#pragma clang diagnostic push
#pragma clang diagnostic ignored"-Warc-performSelector-leaks"
//这里是会报警告的代码
if ([target respondsToSelector:aciton]) {
[target performSelector:aciton];
}
#pragma clang diagnostic pop

if (repeat == NO) {
dispatch_source_cancel(_timer);
}
});
//启动定时器
dispatch_resume(_timer);
//存放到字典
if (name.length && _timer) {
dispatch_semaphore_wait(samephore, DISPATCH_TIME_FOREVER);
timers[name] = _timer;
dispatch_semaphore_signal(samephore);
}
return name;
}
+ (void)exeCancelTask:(NSString *)key{
if (key.length == 0) {
return;
}
dispatch_semaphore_wait(samephore, DISPATCH_TIME_FOREVER);
if ([timers.allKeys containsObject:key]) {
dispatch_source_cancel(timers[key]);
[timers removeObjectForKey:key];
}
dispatch_semaphore_signal(samephore);
}

用的时候很简单

1
2
3
4
5
6
key = [FYTimer exeTask:^{
NSLog(@"123");
} start:1
interval:1
repeat:YES
async:NO];

或者

1
key = [FYTimer exeTask:self sel:@selector(test) start:0 interval:1 repeat:YES async:YES];

取消执行的时候

1
[FYTimer exeCancelTask:key];

测试封装的定时器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- (void)viewDidLoad {
[super viewDidLoad];
key = [FYTimer exeTask:self sel:@selector(test) start:0 interval:1 repeat:YES async:YES];
}
-(void)test{
NSLog(@"%@",[NSThread currentThread]);
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[FYTimer exeCancelTask:key];
}
//log
2019-07-30 21:16:48.639486+0800 day17-定时器2[48817:1300897] <NSThread: 0x6000010ec000>{number = 4, name = (null)}
2019-07-30 21:16:49.640177+0800 day17-定时器2[48817:1300897] <NSThread: 0x6000010ec000>{number = 4, name = (null)}
2019-07-30 21:16:50.639668+0800 day17-定时器2[48817:1300897] <NSThread: 0x6000010ec000>{number = 4, name = (null)}
2019-07-30 21:16:51.639590+0800 day17-定时器2[48817:1300897] <NSThread: 0x6000010ec000>{number = 4, name = (null)}
2019-07-30 21:16:52.156004+0800 day17-定时器2[48817:1300845] -[ViewController touchesBegan:withEvent:]

在点击VC的时候进行取消操作,timer停止。

NSProxy实战

NSProxy其实是除了NSObject的另外一个基类,方法比较少,当找不到方法的时候执行消息转发阶段(因为没有父类),调用函数的流程更短,性能则更好。

问题:ret1ret2分别是多少?

1
2
3
4
5
6
7
ViewController *vc1 =[[ViewController alloc]init];
FYProxy *pro1 =[FYProxy proxyWithTarget:vc1];

FYTimerTarget *tar =[FYTimerTarget proxyWithTarget:vc1];
BOOL ret1 = [pro1 isKindOfClass:ViewController.class];
BOOL ret2 = [tar isKindOfClass:ViewController.class];
NSLog(@"%d %d",ret1,ret2);

我们来分析一下,-(bool)isKindOfClass:(cls)对象函数是判断该对象是否的cls的子类或者该类的实例,这点不容置疑,那么ret1应该是0,ret2应该也是0

首先看FYProxy的实现,forwardInvocationmethodSignatureForSelector,在没有该函数的时候进行消息转发,转发对象是self.target,在该例子中isKindOfClass不存在与FYProxy,所以讲该函数转发给了VC,则BOOL ret1 = [pro1 isKindOfClass:ViewController.class];相当于BOOL ret1 = [ViewController.class isKindOfClass:ViewController.class];,所以答案是1

然后ret2是0,tar是继承于NSObject的,本身有-(bool)isKindOfClass:(cls)函数,所以答案是0。

答案是:ret11ret20

内存分配

内存分为保留段、数据段、堆(↓)、栈(↑)、内核区。

数据段包括

  • 字符串常量:比如NSString * str = @”11”
  • 已初始化数据:已初始化的全局变量、静态变量等
  • 未初始化数据:未初始化的全局变量、静态变量等

栈:函数调用开销、比如局部变量,分配的内存空间地址越来越小。

堆:通过alloc、malloc、calloc等动态分配的空间,分配的空间地址越来越大。

验证:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
int a = 10;
int b ;
int main(int argc, char * argv[]) {
@autoreleasepool {
static int c = 20;
static int d;
int e = 10;
int f;
NSString * str = @"123";
NSObject *obj =[[NSObject alloc]init];
NSLog(@"\na:%p \nb:%p \nc:%p \nd:%p \ne:%p \nf:%p \nobj:%p\n str:%p",&a,&b,&c,&d,&e,&f,obj,str);
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}

//log

a:0x1063e0d98
b:0x1063e0e64
c:0x1063e0d9c
d:0x1063e0e60
e:0x7ffee9820efc
f:0x7ffee9820ef8
obj:0x6000013541a0
str:0x1063e0068

Tagged Pointer

从64bit开始,iOS引入Tagged Pointer技术,用于优化NSNumber、NSDate、NSString等小对象的存储,在没有使用之前,他们需要动态分配内存,维护计数,使用Tagged Pointer之后,NSNumber指针里面的数据变成了Tag+Data,也就是将数值直接存储在了指针中,只有当指针不够存储数据时,才会动态分配内存的方式来存储数据,而且objc_msgSend()能够识别出Tagged Pointer,比如NSNumberintValue方法,直接从指针提取数据,节省了以前的调用的开销。
在iOS中,最高位是1(第64bit),在Mac中,最低有效位是1。
runtime源码中objc-internal.h 370行判断是否使用了优化技术

1
2
3
4
5
static inline void * _Nonnull
_objc_encodeTaggedPointer(uintptr_t ptr)
{
return (void *)(objc_debug_taggedpointer_obfuscator ^ ptr);
}

我们拿来这个可以判断对象是否使用了优化技术。

NSNumbe Tagged Pointer

我们使用几个NSNumber的大小数字来验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#if (TARGET_OS_OSX || TARGET_OS_IOSMAC) && __x86_64__ //mac开发
// 64-bit Mac - tag bit is LSB
# define OBJC_MSB_TAGGED_POINTERS 0
#else
// Everything else - tag bit is MSB
# define OBJC_MSB_TAGGED_POINTERS 1//iOS开发
#endif

#if OBJC_MSB_TAGGED_POINTERS
# define _OBJC_TAG_MASK (1UL<<63)
#else
# define _OBJC_TAG_MASK 1UL
#endif
bool objc_isTaggedPointer(const void * _Nullable ptr)
{
return ((uintptr_t)ptr & _OBJC_TAG_MASK) == _OBJC_TAG_MASK;
}
int main(int argc, char * argv[]) {
@autoreleasepool {
NSNumber *n1 = @2;
NSNumber *n2 = @3;
NSNumber *n3 = @(4);
NSNumber *n4 = @(0x4fffffffff);
NSLog(@"\n%p \n%p \n%p \n%p",n1,n2,n3,n4);
BOOL n1_tag = objc_isTaggedPointer((__bridge const void * _Nullable)(n1));
BOOL n2_tag = objc_isTaggedPointer((__bridge const void * _Nullable)(n2));
BOOL n3_tag = objc_isTaggedPointer((__bridge const void * _Nullable)(n3));
BOOL n4_tag = objc_isTaggedPointer((__bridge const void * _Nullable)(n4));

NSLog(@"\nn1:%d \nn2:%d \nn3:%d \nn4:%d ",n1_tag,n2_tag,n3_tag,n4_tag);
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
//log

0xbf4071e2657ccb95
0xbf4071e2657ccb85
0xbf4071e2657ccbf5
0xbf40751d9a833444
2019-07-30 21:55:52.626317+0800 day17-TaggedPointer[49770:1328036]
n1:1
n2:1
n3:1
n4:0

可以看到n1 n2 n3是经过优化的,而n4是大数字,指针容不下该数值,不能优化。

NSString Tagged Pointer

看下面一道题,运行test1test2会出现什么问题?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- (void)test1{
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (NSInteger i = 0; i < 1000; i ++) {
dispatch_async(queue, ^{
self.name = [NSString stringWithFormat:@"abc"];
});
}
}
- (void)test2{
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (NSInteger i = 0; i < 1000; i ++) {
dispatch_async(queue, ^{
self.name = [NSString stringWithFormat:@"abcsefafaefafafaefe"];
});
}
}

我们先不运行,先分析一下。

首先全局队列异步添加任务会出现多线程并发问题,在并发的时候进行写操作会出现资源竞争问题,另外一个小字符串会出现指针优化问题,小字符串和大字符串切换导致_name结构变化,多线程同时写入和读会导致访问坏内存问题,我们来运行一下

1
Thread: EXC_BAD_ACCESS(code = 1)

直接在子线程崩溃了,崩溃函数是objc_release。符合我们的猜想。

验证NSString Tagged Pointer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- (void)test{
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (NSInteger i = 0; i < 1; i ++) {
dispatch_async(queue, ^{
self.name = [NSString stringWithFormat:@"abc"];
NSLog(@"test1 class:%@",self.name.class);
});
}
}
- (void)test2{
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
for (NSInteger i = 0; i < 1; i ++) {
dispatch_async(queue, ^{
self.name = [NSString stringWithFormat:@"abcsefafaefafafaefe"];
NSLog(@"test2 class:%@",self.name.class);
});
}
}
//log
test1 class:NSTaggedPointerString
test2 class:__NSCFString

可以看到NSString Tagged Pointer在小字符串的时候类是NSTaggedPointerString,经过优化的类,大字符串的类是__NSCFString

copy

拷贝分为浅拷贝和深拷贝,浅拷贝只是引用计数+1,深拷贝是拷贝了一个对象,和之前的 互不影响, 引用计数互不影响。

拷贝目的:产生一个副本对象,跟源对象互不影响
修改源对象,不会影响到副本对象
修改副本对象,不会影响源对象

iOS提供了2中拷贝方法

  1. copy 拷贝出来不可变对象
  2. mutableCopy 拷贝出来可变对象
1
2
3
4
5
6
7
8
9
10
11
void test1(){
NSString *str = @"strstrstrstr";
NSMutableString *mut1 =[str mutableCopy];
[mut1 appendFormat:@"123"];
NSString *str2 = [str copy];
NSLog(@"%p %p %p",str,mut1,str2);
}
//log
str:0x100001040
mut1:0x1007385f0
str2:0x100001040

可以看到strstr2地址一样,没有重新复制出来一份,mut1地址和str不一致,是深拷贝,重新拷贝了一份。

我们把字符串换成其他常用的数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void test2(){
NSArray *array = @[@"123",@"123",@"123",@"123",@"123",@"123",@"123"];
NSMutableArray *mut =[array mutableCopy];
NSString *array2 = [array copy];
NSLog(@"\n%p \n%p\n%p",array,mut,array2);
}
//log
0x102840800
0x1028408a0
0x102840800

void test3(){
NSArray *array = [@[@"123",@"123",@"123",@"123",@"123",@"123",@"123"] mutableCopy];
NSMutableArray *mut =[array mutableCopy];
NSString *array2 = [array copy];
NSLog(@"\n%p \n%p\n%p",array,mut,array2);
}
//log
0x102808720
0x1028088a0
0x1028089a0

从上面可以总结看出来,不变数组拷贝出来不变数组,地址不改变,拷贝出来可变数组地址改变,可变数组拷贝出来不可变数组和可变数组,地址会改变。

我们再换成其他的常用的字典

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void test4(){
NSDictionary *item = @{@"key":@"value"};
NSMutableDictionary *mut =[item mutableCopy];
NSDictionary *item2 = [item copy];
NSLog(@"\n%p \n%p\n%p",item,mut,item2);
}

//log
0x1007789c0
0x100779190
0x1007789c0

void test5(){
NSDictionary *item = [@{@"key":@"value"}mutableCopy];
NSMutableDictionary *mut =[item mutableCopy];
NSDictionary *item2 = [item copy];
NSLog(@"\n%p \n%p\n%p",item,mut,item2);
}
//log

0x1007041d0
0x1007042b0
0x1007043a0

从上面可以总结看出来,不变字典拷贝出来不变字典,地址不改变,拷贝出来可变字典地址改变,可变字典拷贝出来不可变字典和可变字典,地址会改变。

由这几个看出来,总结出来下表

类型 copy mutableCopy
NSString 浅拷贝 深拷贝
NSMutableString 浅拷贝 深拷贝
NSArray 浅拷贝 深拷贝
NSMutableArray 深拷贝 深拷贝
NSDictionary 浅拷贝 深拷贝
NSMutableDictionary 深拷贝 深拷贝

自定义对象实现协议NSCoping

自定义的对象使用copy呢?系统的已经实现了,我们自定义的需要自己去实现,自定义的类继承NSCopying

1
2
3
4
5
6
7
8
9
10
11
@protocol NSCopying

- (id)copyWithZone:(nullable NSZone *)zone;

@end

@protocol NSMutableCopying

- (id)mutableCopyWithZone:(nullable NSZone *)zone;

@end

看到NSCopyingNSMutableCopying这两个协议,对于自定义的可变对象,其实没什么意义,本来自定义的对象的属性,基本都是可变的,所以只需要实现NSCopying协议就好了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@interface FYPerson : NSObject
@property (nonatomic,assign) int age;
@property (nonatomic,assign) int level;

@end

@interface FYPerson()<NSCopying>
@end

@implementation FYPerson
-(instancetype)copyWithZone:(NSZone *)zone{
FYPerson *p=[[FYPerson alloc]init];
p.age = self.age;
p.level = self.level;
return p;
}

@end


FYPerson *p =[[FYPerson alloc]init];
p.age = 10;
p.level = 11;
FYPerson *p2 =[p copy];
NSLog(@"%d %d",p2.age,p2.level);
//log
10 11

自己实现了NSCoping协议完成了对对象的深拷贝,成功将对象的属性复制过去了,当属性多了怎么办?我们可以利用runtime实现一个一劳永逸的方案。

然后将copyWithZone利用runtime遍历所有的成员变量,将所有的变量都赋值,当变量多的时候,这里也不用修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@implementation NSObject (add)
-(instancetype)copyWithZone:(NSZone *)zone{
Class cls = [self class];
NSObject * p=[cls new];
//成员变量个数
unsigned int count;
//赋值成员变量数组
Ivar *ivars = class_copyIvarList(self.class, &count);
//遍历数组
for (int i = 0; i < count; i ++) {
Ivar var = ivars[i];
//获取成员变量名字
const char * name = ivar_getName(var);
if (name != nil) {
NSString *v = [NSString stringWithUTF8String:name];
id value = [self valueForKey:v];
//给新的对象赋值
if (value != NULL) {
[p setValue:value forKey:v];
}
}
}
free(ivars);
return p;
}
@end

FYPerson *p =[[FYPerson alloc]init];
p.age = 10;
p.level = 11;
p.name = @"xiaowang";
FYPerson *p2 =[p copy];
NSLog(@"%d %d %@",p2.age,p2.level,p2.name);

//log
10
11
xiaowang

根据启动顺序,类别的方法在类的方法加载后边,类别中的方法会覆盖类的方法,所以
在基类NSObject在类别中重写了-(instancetype)copyWithZone:(NSZone *)zone方法,子类就不用重写了。达成了一劳永逸的方案。

引用计数原理

摘自百度百科

引用计数是计算机编程语言中的一种内存管理技术,是指将资源(可以是对象、内存或磁盘空间等等)的被引用次数保存起来,当被引用次数变为零时就将其释放的过程。使用引用计数技术可以实现自动资源管理的目的。同时引用计数还可以指使用引用计数技术回收未使用资源的垃圾回收算法

在iOS中,使用引用计数来管理OC对象内存,一个新创建的OC对象的引用计数默认是1,当引用计数减为0,OC对象就会销毁,释放其他内存空间,调用retain会让OC对象的引用计数+1,调用release会让OC对象的引用计数-1。
当调用alloc、new、copy、mutableCopy方法返回一个对象,在不需要这个对象时,要调用release或者autorelease来释放它,想拥有某个对象,就让他的引用计数+1,不再拥有某个对象,就让他引用计数-1.

在MRC中我们经常都是这样子使用的

1
2
3
4
5
FYPerson *p=[[FYPerson alloc]init];
FYPerson *p2 =[p retain];
//code here
[p release];
[p2 release];

但是在ARC中是系统帮我们做了自动引用计数,不用开发者做很多繁琐的事情了,我们就探究下引用计数是怎么实现的。

引用计数存储在isa指针中的extra_rc,存储值大于这个范围的时候,则bits.has_sidetable_rc=1然后将剩余的RetainCount存储到全局的tablekeyself对应的值。

Retainruntime源码查找函数路径objc_object::retain()->objc_object::rootRetain()->objc_object::rootRetain(bool, bool)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
//大概率x==1 提高读取指令的效率
#define fastpath(x) (__builtin_expect(bool(x), 1))
//大概率x==0 提高读取指令的效率
#define slowpath(x) (__builtin_expect(bool(x), 0))


//引用计数+1
//tryRetain 尝试+1
//handleOverflow 是否覆盖
ALWAYS_INLINE id objc_object::rootRetain(bool tryRetain, bool handleOverflow)
{
//优化的指针 返回this
if (isTaggedPointer()) return (id)this;

bool sideTableLocked = false;
bool transcribeToSideTable = false;

isa_t oldisa;
isa_t newisa;

do {
transcribeToSideTable = false;
//old bits
oldisa = LoadExclusive(&isa.bits);
newisa = oldisa;
//使用联合体技术
if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa.bits);//nothing
if (!tryRetain && sideTableLocked) sidetable_unlock();//解锁
if (tryRetain) return sidetable_tryRetain() ? (id)this : nil;
else return sidetable_retain();////sidetable 引用计数+1
}
// don't check newisa.fast_rr; we already called any RR overrides
//不尝试retain 和 正在销毁 什么都不做 返回 nil
if (slowpath(tryRetain && newisa.deallocating)) {
ClearExclusive(&isa.bits);
if (!tryRetain && sideTableLocked) sidetable_unlock();
return nil;
}
uintptr_t carry;
//引用计数+1 (bits.extra_rc++;)
newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++

if (slowpath(carry)) {
// newisa.extra_rc++ 溢出处理
if (!handleOverflow) {
ClearExclusive(&isa.bits);
return rootRetain_overflow(tryRetain);
}
//为拷贝到side table 做准备
if (!tryRetain && !sideTableLocked) sidetable_lock();
sideTableLocked = true;
transcribeToSideTable = true;
newisa.extra_rc = RC_HALF;
newisa.has_sidetable_rc = true;
}
} while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)));

if (slowpath(transcribeToSideTable)) {
//拷贝 平外一半的 引用计数到 side table
sidetable_addExtraRC_nolock(RC_HALF);
}

if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock();
return (id)this;
}

//sidetable 引用计数+1
id objc_object::sidetable_retain()
{
#if SUPPORT_NONPOINTER_ISA
assert(!isa.nonpointer);
#endif
//取出table key=this
SideTable& table = SideTables()[this];

table.lock();
size_t& refcntStorage = table.refcnts[this];
if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) {
refcntStorage += SIDE_TABLE_RC_ONE;
}
table.unlock();

return (id)this;
}

引用计数+1,判断了需要是指针没有优化和isa有没有使用的联合体技术,然后将判断是否溢出,溢出的话,将extra_rc的值复制到side table中,设置参数isa->has_sidetable_rc=true

引用计数-1,在runtime源码中查找路径是objc_object::release()->objc_object::rootRelease()->objc_object::rootRelease(bool performDealloc, bool handleUnderflow),我们进入到函数内部

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
ALWAYS_INLINE bool  objc_object::rootRelease(bool performDealloc, bool handleUnderflow)
{
if (isTaggedPointer()) return false;//指针优化的不存在计数器

bool sideTableLocked = false;

isa_t oldisa;
isa_t newisa;

retry:
do {//isa
oldisa = LoadExclusive(&isa.bits);
newisa = oldisa;
if (slowpath(!newisa.nonpointer)) {
ClearExclusive(&isa.bits);
if (sideTableLocked) sidetable_unlock();
//side table -1
return sidetable_release(performDealloc);
}
uintptr_t carry;
newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); // extra_rc--
if (slowpath(carry)) {
// don't ClearExclusive()
goto underflow;
}
} while (slowpath(!StoreReleaseExclusive(&isa.bits,
oldisa.bits, newisa.bits)));

if (slowpath(sideTableLocked)) sidetable_unlock();
return false;

underflow:
newisa = oldisa;

if (slowpath(newisa.has_sidetable_rc)) {
if (!handleUnderflow) {
ClearExclusive(&isa.bits);
return rootRelease_underflow(performDealloc);
}

if (!sideTableLocked) {
ClearExclusive(&isa.bits);
sidetable_lock();
sideTableLocked = true;
goto retry;
}

//side table 引用计数-1
size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF);

if (borrowed > 0) {
newisa.extra_rc = borrowed - 1; // redo the original decrement too
bool stored = StoreReleaseExclusive(&isa.bits,
oldisa.bits, newisa.bits);
if (!stored) {
isa_t oldisa2 = LoadExclusive(&isa.bits);
isa_t newisa2 = oldisa2;
if (newisa2.nonpointer) {
uintptr_t overflow;
newisa2.bits =
addc(newisa2.bits, RC_ONE * (borrowed-1), 0, &overflow);
if (!overflow) {
stored = StoreReleaseExclusive(&isa.bits, oldisa2.bits,
newisa2.bits);
}
}
}

if (!stored) {
// Inline update failed.
// Put the retains back in the side table.
sidetable_addExtraRC_nolock(borrowed);
goto retry;
}

sidetable_unlock();
return false;
}
else {
// Side table is empty after all. Fall-through to the dealloc path.
}
}

//真正的销毁

if (slowpath(newisa.deallocating)) {
ClearExclusive(&isa.bits);
if (sideTableLocked) sidetable_unlock();
return overrelease_error();
// does not actually return
}
//设置正在销毁
newisa.deallocating = true;
if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry;

if (slowpath(sideTableLocked)) sidetable_unlock();

__sync_synchronize();
if (performDealloc) {
//销毁
((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc);
}
return true;
}

看了上边了解到引用计数分两部分,extra_rcside table,探究一下
rootRetainCount()的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
inline uintptr_t  objc_object::rootRetainCount()
{
//优化指针 直接返回
if (isTaggedPointer()) return (uintptr_t)this;
//没优化则 到SideTable 读取
sidetable_lock();
//isa指针
isa_t bits = LoadExclusive(&isa.bits);
ClearExclusive(&isa.bits);//啥都没做
if (bits.nonpointer) {//使用联合体存储更多的数据
uintptr_t rc = 1 + bits.extra_rc;//计数数量
if (bits.has_sidetable_rc) {//当大过于 联合体存储的值 则另外在SideTable读取数据
//读取table的值 相加
rc += sidetable_getExtraRC_nolock();
}
sidetable_unlock();
return rc;
}

sidetable_unlock();
//在sidetable 中存储的count
return sidetable_retainCount();
}

当是存储小数据的时候,指针优化,则直接返回self,大数据的话,则table加锁,
class优化的之后使用联合体存储更多的数据,class没有优化则直接去sizedable读取数据。
优化了则在sidetable_getExtraRC_nolock()读取数据

1
2
3
4
5
6
7
8
9
10
11
12
//使用联合体
size_t objc_object::sidetable_getExtraRC_nolock()
{
//不是联合体技术 则报错
assert(isa.nonpointer);
//key是 this,存储了每个对象的table
SideTable& table = SideTables()[this];
//找到 it 否则返回0
RefcountMap::iterator it = table.refcnts.find(this);
if (it == table.refcnts.end()) return 0;
else return it->second >> SIDE_TABLE_RC_SHIFT;
}

没有优化的是直接读取

1
2
3
4
5
6
7
8
9
10
11
12
13
//未使用联合体的情况,
uintptr_t objc_object::sidetable_retainCount()
{//没有联合体存储的计数器则直接在table中取出来
SideTable& table = SideTables()[this];
size_t refcnt_result = 1;
table.lock();
RefcountMap::iterator it = table.refcnts.find(this);
if (it != table.refcnts.end()) {
refcnt_result += it->second >> SIDE_TABLE_RC_SHIFT;
}
table.unlock();
return refcnt_result;
}

weak指针原理

当一个对象要销毁的时候会调用dealloc,调用轨迹是dealloc->_objc_rootDealloc->object_dispose->objc_destructInstance->free
我们进入到objc_destructInstance内部

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void *objc_destructInstance(id obj) 
{
if (obj) {
// Read all of the flags at once for performance.
//c++析构函数
bool cxx = obj->hasCxxDtor();
//关联函数
bool assoc = obj->hasAssociatedObjects();

// This order is important.
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj);
obj->clearDeallocating();
}
return obj;
}

销毁了c++析构函数和关联函数最后进入到clearDeallocating,我们进入到函数内部

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//正在清除side table 和weakly referenced
inline void
objc_object::clearDeallocating()
{
if (slowpath(!isa.nonpointer)) {
// Slow path for raw pointer isa.
//释放weak
sidetable_clearDeallocating();
}
else if (slowpath(isa.weakly_referenced || isa.has_sidetable_rc)) {
// Slow path for non-pointer isa with weak refs and/or side table data.
//释放weak 和引用计数
clearDeallocating_slow();
}
assert(!sidetable_present());
}

最终调用了sidetable_clearDeallocatingclearDeallocating_slow实现销毁weak和引用计数side table

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
NEVER_INLINE void
objc_object::clearDeallocating_slow()
{
assert(isa.nonpointer && (isa.weakly_referenced || isa.has_sidetable_rc));

SideTable& table = SideTables()[this];
table.lock();
//清除weak
if (isa.weakly_referenced) {
//table.weak_table 弱引用表
weak_clear_no_lock(&table.weak_table, (id)this);
}
//引用计数
if (isa.has_sidetable_rc) {
//擦除 this
table.refcnts.erase(this);
}
table.unlock();
}

其实weak修饰的对象会存储在全局的SideTable,当对象销毁的时候会在SideTable进行查找,时候有weak对象,有的话则进行销毁。

Autoreleasepool 原理

Autoreleasepool中文名自动释放池,里边装着一些变量,当池子不需要(销毁)的时候,release里边的对象(引用计数-1)。
我们将下边的代码转化成c++

1
2
3
@autoreleasepool {
FYPerson *p = [[FYPerson alloc]init];
}

使用xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -f main.m
转成c++

1
2
3
4
5
/* @autoreleasepool */ {
__AtAutoreleasePool __autoreleasepool;
FYPerson *p = ((FYPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((FYPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("FYPerson"), sel_registerName("alloc")), sel_registerName("init"));

}

__AtAutoreleasePool是一个结构体

1
2
3
4
5
6
7
8
9
struct __AtAutoreleasePool {
__AtAutoreleasePool() {//构造函数 生成结构体变量的时候调用
atautoreleasepoolobj = objc_autoreleasePoolPush();
}
~__AtAutoreleasePool() {//析构函数 销毁的时候调用
objc_autoreleasePoolPop(atautoreleasepoolobj);
}
void * atautoreleasepoolobj;
};

然后将上边的代码和c++整合到一起就是这样子

1
2
3
4
5
{
__AtAutoreleasePool pool = objc_autoreleasePoolPush();
FYPerson *p = [[FYPerson alloc]init];
objc_autoreleasePoolPop(pool)
}

在进入大括号生成一个释放池,离开大括号则释放释放池,我们再看一下释放函数是怎么工作的,在runtime源码中NSObject.mm 1848 行

1
2
3
4
void objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}

pop实现了AutoreleasePoolPage中的对象的释放,想了解怎么释放的可以研究下源码runtime NSObject.mm 1063行

其实AutoreleasePoolAutoreleasePoolPage来管理的,AutoreleasePoolpage结构如下

1
2
3
4
5
6
7
8
9
class AutoreleasePoolPage {
magic_t const magic;
id *next;//下一个存放aotoreleass对象的地址
pthread_t const thread;//线程
AutoreleasePoolPage * const parent; //父节点
AutoreleasePoolPage *child;//子节点
uint32_t const depth;//深度
uint32_t hiwat;
}

AutoreleasePoolPage在初始化在autoreleaseNewPage申请了4096字节除了自己变量的空间,AutoreleasePoolPage是一个C++实现的类

  • 内部使用id *next指向了栈顶最新add进来的autorelease对象的下一个位置
  • 一个AutoreleasePoolPage的空间被占满时,会新建一个AutoreleasePoolPage对象,连接链表,后来的autorelease对象在新的page加入
  • AutoreleasePoolPage每个对象会开辟4096字节内存(也就是虚拟内存一页的大小),除了上面的实例变量所占空间,剩下的空间全部用来储存autorelease对象的地址
  • AutoreleasePool是按线程一一对应的(结构中的thread指针指向当前线程)
  • AutoreleasePool并没有单独的结构,而是由若干个AutoreleasePoolPage以双向链表的形式组合而成(分别对应结构中的parent指针和child指针)

其他的都是自动释放池的其他对象的指针,我们使用_objc_autoreleasePoolPrint()可以查看释放池的存储内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
extern void _objc_autoreleasePoolPrint(void);
int main(int argc, const char * argv[]) {
@autoreleasepool {//r1 = push()

FYPerson *p = [[FYPerson alloc]init];
_objc_autoreleasePoolPrint();
printf("\n--------------\n");
}//pop(r1)
return 0;
}
//log

objc[23958]: ##############
objc[23958]: AUTORELEASE POOLS for thread 0x1000aa5c0
objc[23958]: 3 releases pending.
objc[23958]: [0x101000000] ................ PAGE (hot) (cold)
objc[23958]: [0x101000038] ################ POOL 0x101000038
objc[23958]: [0x101000040] 0x10050cfa0 FYPerson
objc[23958]: [0x101000048] 0x10050cdb0 FYPerson
objc[23958]: ##############

--------------

可以看到存储了3 releases pending一个对象,而且大小都8字节。再看一个复杂的,自动释放池嵌套自动释放池

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
int main(int argc, const char * argv[]) {
@autoreleasepool {//r1 = push()

FYPerson *p = [[[FYPerson alloc]init] autorelease];
FYPerson *p2 = [[[FYPerson alloc]init] autorelease];
@autoreleasepool {//r1 = push()

FYPerson *p3 = [[[FYPerson alloc]init] autorelease];
FYPerson *p4 = [[[FYPerson alloc]init] autorelease];

_objc_autoreleasePoolPrint();
printf("\n--------------\n");
}//pop(r1)
}//pop(r1)
return 0;
}
//log
objc[24025]: ##############
objc[24025]: AUTORELEASE POOLS for thread 0x1000aa5c0
objc[24025]: 6 releases pending.
objc[24025]: [0x100803000] ................ PAGE (hot) (cold)
objc[24025]: [0x100803038] ################ POOL 0x100803038
objc[24025]: [0x100803040] 0x100721580 FYPerson
objc[24025]: [0x100803048] 0x100721b10 FYPerson
objc[24025]: [0x100803050] ################ POOL 0x100803050
objc[24025]: [0x100803058] 0x100721390 FYPerson
objc[24025]: [0x100803060] 0x100717620 FYPerson
objc[24025]: ##############

看到了2个POOL和四个FYPerson对象,一共是6个对象,当出了释放池会执行release

当无优化的指针调用autorelease其实是调用了AutoreleasePoolPage::autorelease((id)this)->autoreleaseFast(obj)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static inline id *autoreleaseFast(id obj)
{
AutoreleasePoolPage *page = hotPage();
//当有分页而且分页没有满就添加
if (page && !page->full()) {
return page->add(obj);
} else if (page) {
//满则新建一个page进行添加obj和设置hotpage
return autoreleaseFullPage(obj, page);
} else {
//没有page则新建page进行添加
return autoreleaseNoPage(obj);
}
}

MRC
autorealease修饰的是的对象在没有外部添加到自动释放池的时候,在runloop循环的时候会销毁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0),
kCFRunLoopBeforeTimers = (1UL << 1),
kCFRunLoopBeforeSources = (1UL << 2),
kCFRunLoopBeforeWaiting = (1UL << 5),
kCFRunLoopAfterWaiting = (1UL << 6),
kCFRunLoopExit = (1UL << 7),
kCFRunLoopAllActivities = 0x0FFFFFFFU
};

//activities = 0xa0转化成二进制 0b101 0000
系统监听了mainRunloop 的 kCFRunLoopBeforeWaiting 和kCFRunLoopExit两种状态来更新autorelease的数据
//回调函数是 _wrapRunLoopWithAutoreleasePoolHandler

"<CFRunLoopObserver 0x600002538320 [0x10ce45ae8]>{valid = Yes, activities = 0xa0,
repeats = Yes, order = 2147483647,
callout = _wrapRunLoopWithAutoreleasePoolHandler (0x10f94087d),
context = <CFArray 0x600001a373f0 [0x10ce45ae8]>{type = mutable-small, count = 1,
values = (\n\t0 : <0x7fb6dc004058>\n)}}"

activities = 0xa0转化成二进制 0b101 0000
系统监听了mainRunloopkCFRunLoopBeforeWaitingkCFRunLoopExit两种状态来更新autorelease的数据
回调函数是 _wrapRunLoopWithAutoreleasePoolHandler

1
2
3
void test(){
FYPerson *p =[[FYPerson alloc]init];
}

p对象在某次循环中push,在循环到kCFRunLoopBeforeWaiting进行一次pop,则上次循环的autolease对象没有其他对象retain的进行释放。并不是出了test()立马释放。

在ARC中则执行完毕test()会马上释放。

总结

  • 当重复创建对象或者代码段不容易管理生命周期使用自动释放池是不错的选择。
  • 存在在全局的SideTable中weak修饰的对象会在dealloc函数执行过程中检测或销毁该对象。
  • 可变对象拷贝一定会生成已新对象,不可变对象拷贝成不可变对象则是引用计数+1。
  • 优化的指向对象的指针,不用走objc_msgSend()的消息流程从而提高性能。
  • CADisplayLinkTimer本质是加到loop循环当中,依附于循环,没有runloop,则不能正确执行,使用runloop需要注意循环引用和runloop所在的线程的释放问题。

参考资料

  • 黑幕背后的Autorelease
  • 小码哥视频
  • iOS和OS多线程与内存管理
  • iOS和macOS性能优化

    资料下载

  • 学习资料下载git
  • demo code git
  • runtime可运行的源码git

最怕一生碌碌无为,还安慰自己平凡可贵。

广告时间