前言
对于 NSThread 开启的子线程,我们需要在 main 函数中创建一个autoreleasepool,当我们从其他线程跳转到该线程执行时,对象是如何释放的呢?主线程是由于runloop的循环,在beforeWait时,触发主线程的autoreleasepool的pop和push操作来释放的,而子线程并没有自动添加这些observer,那么如何释放的?

探索
跳转到我们线程执行任务的方法如下,使用了performSelector:系列的方法。

1 OBJC_EXTERN void _objc_autoreleasePoolPrint(void);
2 @implementation GrowingDispatchManager
3
4 + (void)dispatchInGrowingThread:(void (^_Nullable)(void))block {
5     if ([[NSThread currentThread] isEqual:[GrowingThread sharedThread]]) {
6         block();
7     } else {
8         [GrowingDispatchManager performSelector:@selector(dispatchBlock:)
9                                        onThread:[GrowingThread sharedThread]
10                                      withObject:block
11                                   waitUntilDone:NO];
12     }
13 }
14
15 + (void)dispatchBlock:(void (^_Nullable)(void))block {
16
17     NSLog(@”runloop %@”,[NSRunLoop currentRunLoop]);
18 //    _objc_autoreleasePoolPrint();
19     if (block) {
20         block();
21     }
22 //    _objc_autoreleasePoolPrint();
23 }

打印当前runloop,确定是没有像主线程那样添加observer,如下:

1 2021-02-03 23:14:42.112259+0800 Example[5075:57255] runloop <CFRunLoop 0x600000dbdb00 [0x7fff80617cb0]>{wakeup port = 0x9203, stopped = false, ignoreWakeUps = false,
2 current mode = kCFRunLoopDefaultMode,
3 common modes = <CFBasicHash 0x600003fe25b0 [0x7fff80617cb0]>{type = mutable set, count = 1,
4 entries =>
5
6 }
7 ,
8 common mode items = <CFBasicHash 0x600003fe2bb0 [0x7fff80617cb0]>{type = mutable set, count = 1,
9 entries =>
10
11 }
12 ,
13 modes = <CFBasicHash 0x600003fe27f0 [0x7fff80617cb0]>{type = mutable set, count = 1,
14 entries =>
15
16
17 FBasicHash 0x600003fe25e0 [0x7fff80617cb0]>{type = mutable set, count = 2,
18 entries =>
19
20
21 }
22 ,
23
24 entries =>
25 }
26 ,
27
28
29
30 },
31
32 }
33 }

那么如何释放的呢,通过_objc_autoreleasePoolPrint()打印自动释放池堆栈,确定对象没有堆积,是进行释放过了。

常驻线程实现代码如下:

2 // GrowingThread.m
3 // GrowingAnalytics
4
5 #import “GrowingThread.h”
6
7 @interface GrowingThread () {
8     dispatch_group_t _waitGroup;
9 }
10
11 @property (nonatomic, strong, readwrite) NSRunLoop *runLoop;
12
13 @end
14
15 @implementation GrowingThread
16
17 + (instancetype)sharedThread {
18     static GrowingThread *thread;
19     static dispatch_once_t onceToken;
20     dispatch_once(&onceToken, ^{
21         thread = [[GrowingThread alloc] init];
22         thread.name = @”com.growing.thread”;
23         [thread start];
24     });
25     return thread;
26 }
27
28 – (instancetype)init {
29     self = [super init];
30     if (self) {
31         _waitGroup = dispatch_group_create();
32         dispatch_group_enter(_waitGroup);
33     }
34     return self;
35 }
36
37 – (void)main {
38     @autoreleasepool {
39         _runLoop = [NSRunLoop currentRunLoop];
40         dispatch_group_leave(_waitGroup);
41
42         // Add an empty run loop source to prevent runloop from spinning.
43         CFRunLoopSourceContext sourceCtx = {.version = 0,
44                                             .info = NULL,
45                                             .retain = NULL,
46                                             .release = NULL,
47                                             .copyDescription = NULL,
48                                             .equal = NULL,
49                                             .hash = NULL,
50                                             .schedule = NULL,
51                                             .cancel = NULL,
52                                             .perform = NULL};
53         CFRunLoopSourceRef source = CFRunLoopSourceCreate(NULL, 0, &sourceCtx);
54         CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
55         CFRelease(source);
56
57         while ([_runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]) {
58         }
59         assert(NO);
60     }
61 }
62
63 – (NSRunLoop *)runLoop;
64 {
65     dispatch_group_wait(_waitGroup, DISPATCH_TIME_FOREVER);
66     return _runLoop;
67 }
68
69 @end

通过断点调试,发现堆栈中确实有调用对应的NSPushAutoreleasePool和NSPopAutoreleasePool

%title插图%num
打印执行时的堆栈:

1 (lldb) bt
2 * thread #9, name = ‘com.growing.thread’, stop reason = breakpoint 2.1
3     frame #0: 0x0000000108cf5c07 GrowingAnalytics`+[GrowingDispatchManager dispatchBlock:](self=GrowingDispatchManager, _cmd=”dispatchBlock:”, block=0x0000000108cfb330) at GrowingDispatchManager.m:46:1
4     frame #1: 0x00007fff25781c30 Foundation`__NSThreadPerformPerform + 259
5     frame #2: 0x00007fff23bd4471 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
6     frame #3: 0x00007fff23bd439c CoreFoundation`__CFRunLoopDoSource0 + 76
7     frame #4: 0x00007fff23bd3b74 CoreFoundation`__CFRunLoopDoSources0 + 180
8     frame #5: 0x00007fff23bce87f CoreFoundation`__CFRunLoopRun + 1263
9     frame #6: 0x00007fff23bce066 CoreFoundation`CFRunLoopRunSpecific + 438
10   * frame #7: 0x00007fff2576b86f Foundation`-[NSRunLoop(NSRunLoop) runMode:beforeDate:] + 211
11     frame #8: 0x0000000108d41012 GrowingAnalytics`-[GrowingThread main](self=0x0000600001285900, _cmd=”main”) at GrowingThread.m:72:16
12     frame #9: 0x00007fff257817a7 Foundation`__NSThread__start__ + 1047
13     frame #10: 0x00007fff52466e65 libsystem_pthread.dylib`_pthread_start + 148
14     frame #11: 0x00007fff5246283b libsystem_pthread.dylib`thread_start + 15

至此,对象为何释放的原因找到了,如果不是runMode:beforeDate:方法自动加上了自动释放池,就内存泄漏了,所以需要在执行的方法dispatchBlock中保险起见,都加上@autoreleasepool才是上策,这里也是忽略了这点,险些bug背锅。。。