iOS开发-NSThread子线程autoreleasepool的问题

前言

对于 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

通过断点调试,发现堆栈中确实有调用对应的NSPushAutoreleasePoolNSPopAutoreleasePool
在这里插入图片描述

打印执行时的堆栈:

(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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值