前言
对于 NSThread 开启的子线程,我们需要在 main 函数中创建一个autoreleasepool
,当我们从其他线程跳转到该线程执行时,对象是如何释放的呢?主线程是由于runloop
的循环,在beforeWait
时,触发主线程的autoreleasepool
的pop和push操作来释放的,而子线程并没有自动添加这些observer,那么如何释放的?
探索
跳转到我们线程执行任务的方法如下,使用了performSelector:
系列的方法。
OBJC_EXTERN void _objc_autoreleasePoolPrint(void);
@implementation GrowingDispatchManager
+ (void)dispatchInGrowingThread:(void (^_Nullable)(void))block {
if ([[NSThread currentThread] isEqual:[GrowingThread sharedThread]]) {
block();
} else {
[GrowingDispatchManager performSelector:@selector(dispatchBlock:)
onThread:[GrowingThread sharedThread]
withObject:block
waitUntilDone:NO];
}
}
+ (void)dispatchBlock:(void (^_Nullable)(void))block {
//这里并没有添加autoreleasepool
NSLog(@"runloop %@",[NSRunLoop currentRunLoop]);
// _objc_autoreleasePoolPrint();
if (block) {
block();
}
// _objc_autoreleasePoolPrint();
}
打印当前runloop
,确定是没有像主线程那样添加observer
,如下:
2021-02-03 23:14:42.112259+0800 Example[5075:57255] runloop <CFRunLoop 0x600000dbdb00 [0x7fff80617cb0]>{wakeup port = 0x9203, stopped = false, ignoreWakeUps = false,
current mode = kCFRunLoopDefaultMode,
common modes = <CFBasicHash 0x600003fe25b0 [0x7fff80617cb0]>{type = mutable set, count = 1,
entries =>
2 : <CFString 0x7fff8062b0a0 [0x7fff80617cb0]>{contents = "kCFRunLoopDefaultMode"}
}
,
common mode items = <CFBasicHash 0x600003fe2bb0 [0x7fff80617cb0]>{type = mutable set, count = 1,
entries =>
2 : <CFRunLoopSource 0x6000004bf180 [0x7fff80617cb0]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource context>{version = 0, info = 0x6000033ac4f0, callout = __NSThreadPerformPerform (0x7fff25781b2d)}}
}
,
modes = <CFBasicHash 0x600003fe27f0 [0x7fff80617cb0]>{type = mutable set, count = 1,
entries =>
2 : <CFRunLoopMode 0x600000a9c9c0 [0x7fff80617cb0]>{name = kCFRunLoopDefaultMode, port set = 0x6303, queue = 0x600001fa8b00, source = 0x600001fa8c00 (not fired), timer port = 0x6403,
sources0 = <C2021-02-03 23:14:42:122 Example[5075:57027] <UITabBarController: 0x7fed38839200> you want find parentVC is nil
FBasicHash 0x600003fe25e0 [0x7fff80617cb0]>{type = mutable set, count = 2,
entries =>
0 : <CFRunLoopSource 0x6000004b4c00 [0x7fff80617cb0]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = ??? (0x0)}}
2 : <CFRunLoopSource 0x6000004bf180 [0x7fff80617cb0]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource context>{version = 0, info = 0x6000033ac4f0, callout = __NSThreadPerformPerform (0x7fff25781b2d)}}
}
,
sources1 = <CFBasicHash 0x600003fe2880 [0x7fff80617cb0]>{type = mutable set, count = 0,
entries =>
}
,
observers = (null),
timers = (null),
currently 634058082 (3152466695774) / soft deadline in: 1.84467409e+10 sec (@ -1) / hard deadline in: 1.84467409e+10 sec (@ -1)
},
}
}
那么如何释放的呢,通过_objc_autoreleasePoolPrint()
打印自动释放池堆栈,确定对象没有堆积,是进行释放过了。
常驻线程实现代码如下:
//
// GrowingThread.m
// GrowingAnalytics
#import "GrowingThread.h"
@interface GrowingThread () {
dispatch_group_t _waitGroup;
}
@property (nonatomic, strong, readwrite) NSRunLoop *runLoop;
@end
@implementation GrowingThread
+ (instancetype)sharedThread {
static GrowingThread *thread;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
thread = [[GrowingThread alloc] init];
thread.name = @"com.growing.thread";
[thread start];
});
return thread;
}
- (instancetype)init {
self = [super init];
if (self) {
_waitGroup = dispatch_group_create();
dispatch_group_enter(_waitGroup);
}
return self;
}
- (void)main {
@autoreleasepool {
_runLoop = [NSRunLoop currentRunLoop];
dispatch_group_leave(_waitGroup);
// Add an empty run loop source to prevent runloop from spinning.
CFRunLoopSourceContext sourceCtx = {.version = 0,
.info = NULL,
.retain = NULL,
.release = NULL,
.copyDescription = NULL,
.equal = NULL,
.hash = NULL,
.schedule = NULL,
.cancel = NULL,
.perform = NULL};
CFRunLoopSourceRef source = CFRunLoopSourceCreate(NULL, 0, &sourceCtx);
CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
CFRelease(source);
while ([_runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]) {
}
assert(NO);
}
}
- (NSRunLoop *)runLoop;
{
dispatch_group_wait(_waitGroup, DISPATCH_TIME_FOREVER);
return _runLoop;
}
@end
通过断点调试,发现堆栈中确实有调用对应的NSPushAutoreleasePool
和NSPopAutoreleasePool
打印执行时的堆栈:
(lldb) bt
* thread #9, name = 'com.growing.thread', stop reason = breakpoint 2.1
frame #0: 0x0000000108cf5c07 GrowingAnalytics`+[GrowingDispatchManager dispatchBlock:](self=GrowingDispatchManager, _cmd="dispatchBlock:", block=0x0000000108cfb330) at GrowingDispatchManager.m:46:1
frame #1: 0x00007fff25781c30 Foundation`__NSThreadPerformPerform + 259
frame #2: 0x00007fff23bd4471 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
frame #3: 0x00007fff23bd439c CoreFoundation`__CFRunLoopDoSource0 + 76
frame #4: 0x00007fff23bd3b74 CoreFoundation`__CFRunLoopDoSources0 + 180
frame #5: 0x00007fff23bce87f CoreFoundation`__CFRunLoopRun + 1263
frame #6: 0x00007fff23bce066 CoreFoundation`CFRunLoopRunSpecific + 438
* frame #7: 0x00007fff2576b86f Foundation`-[NSRunLoop(NSRunLoop) runMode:beforeDate:] + 211
frame #8: 0x0000000108d41012 GrowingAnalytics`-[GrowingThread main](self=0x0000600001285900, _cmd="main") at GrowingThread.m:72:16
frame #9: 0x00007fff257817a7 Foundation`__NSThread__start__ + 1047
frame #10: 0x00007fff52466e65 libsystem_pthread.dylib`_pthread_start + 148
frame #11: 0x00007fff5246283b libsystem_pthread.dylib`thread_start + 15
Foundation __NSThreadPerformPerform
是属于Fundation的方法,我们去Fundation源码 中查看,最终发现,runMode:beforeDate:
会给我们自动添加上autoreleasepool
- (BOOL) runMode: (NSString*)mode beforeDate: (NSDate*)date
{
NSAutoreleasePool *arp = [NSAutoreleasePool new];
NSString *savedMode = _currentMode;
GSRunLoopCtxt *context;
NSDate *d;
NSAssert(mode != nil, NSInvalidArgumentException);
/* Process any pending notifications.
*/
GSPrivateNotifyASAP(mode);
/* And process any performers scheduled in the loop (eg something from
* another thread.
*/
_currentMode = mode;
context = NSMapGet(_contextMap, mode);
[self _checkPerformers: context];
_currentMode = savedMode;
/* Find out how long we can wait before first limit date.
* If there are no input sources or timers, return immediately.
*/
d = [self limitDateForMode: mode];
if (nil == d)
{
[arp drain];
return NO;
}
/* Use the earlier of the two dates we have (nil date is like distant past).
*/
if (nil == date)
{
[self acceptInputForMode: mode beforeDate: nil];
}
else
{
/* Retain the date in case the firing of a timer (or some other event)
* releases it.
*/
d = [[d earlierDate: date] copy];
[self acceptInputForMode: mode beforeDate: d];
RELEASE(d);
}
[arp drain];
return YES;
}
至此,对象为何释放的原因找到了,如果不是runMode:beforeDate:
方法自动加上了自动释放池,就内存泄漏了,所以需要在执行的方法dispatchBlock
中保险起见,都加上@autoreleasepool
才是上策,这里也是忽略了这点,险些bug背锅。。。
参考
https://blog.csdn.net/shengpeng3344/article/details/95993638
https://blog.csdn.net/shengpeng3344/article/details/94651706