NSThread 现在会自动创建 autoreleasepool 吗?

Posted

技术标签:

【中文标题】NSThread 现在会自动创建 autoreleasepool 吗?【英文标题】:does NSThread create autoreleasepool automatically now? 【发布时间】:2014-07-25 09:35:36 【问题描述】:

我有这样的测试代码

- (void)viewDidLoad

    [super viewDidLoad];
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(test) object:nil];
    [thread start];


-(void)test

    MyClass *my = [[[MyClass alloc] init] autorelease];
    NSLog(@"%@",[my description]);

我没有为我自己的线程创建任何自动释放池,但是当线程退出时,对象“我的”只是 dealloc。为什么?

即使我将测试代码更改如下

- (void)viewDidLoad

    [super viewDidLoad];

    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(test) object:nil];
    [thread start];
 

-(void)test

    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    MyClass *my = [[[MyClass alloc] init] autorelease];
    NSLog(@"%@",[my description]);

我创建了自己的自动释放池,但在线程退出时不会耗尽它。无论如何,对象“我的”仍然可以解除分配。为什么?

我使用 Xcode5 而不是 ARC

【问题讨论】:

你的我的对象怎么被释放了? 你能得到所需的信息吗? 因为我在对象“my”的 dealloc 方法中有一个断点 【参考方案1】:

没有记录,但在 OS X 10.9+ 和 ios 7+ 上,答案似乎是

Objective-C 运行时是open-source,因此您可以阅读源代码以了解发生了什么。如果您在当前线程上执行没有池的autorelease,则最新版本的运行时(646,随 OS X 10.10 和 iOS 8 一起提供)确实会添加一个池。在NSObject.mm:

static __attribute__((noinline))
id *autoreleaseNoPage(id obj)

    // No pool in place.
    assert(!hotPage());

    if (obj != POOL_SENTINEL  &&  DebugMissingPools) 
        // We are pushing an object with no pool in place, 
        // and no-pool debugging was requested by environment.
        _objc_inform("MISSING POOLS: Object %p of class %s "
                     "autoreleased with no pool in place - "
                     "just leaking - break on "
                     "objc_autoreleaseNoPool() to debug", 
                     (void*)obj, object_getClassName(obj));
        objc_autoreleaseNoPool(obj);
        return nil;
    

    // Install the first page.
    AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
    setHotPage(page);

    // Push an autorelease pool boundary if it wasn't already requested.
    if (obj != POOL_SENTINEL) 
        page->add(POOL_SENTINEL);
    

    // Push the requested object.
    return page->add(obj);

当你推送第一个池(在这种情况下推送的东西是POOL_SENTINEL),或者你在没有池的情况下自动释放时调用这个函数。当第一个池被推送时,它会设置自动释放堆栈。但是从代码中可以看出,只要不设置DebugMissingPools环境变量(默认不设置),当autorelease在没有pool的情况下完成时,也会设置autorelease栈,然后push一个pool(推送POOL_SENTINEL)。

同样,(如果不看其他代码就有点难以理解,但这是相关部分)当线程被销毁(并且线程本地存储被销毁)时,它会释放自动释放堆栈中的所有内容(这就是pop(0); 所做的)所以它不依赖用户弹出最后一个池:

static void tls_dealloc(void *p) 

    // reinstate TLS value while we work
    setHotPage((AutoreleasePoolPage *)p);
    pop(0);
    setHotPage(nil);

之前版本的运行时(551.1,随 OS X 10.9 和 iOS 7 一起提供)也这样做了,正如您从其 NSObject.mm 中看到的那样:

static __attribute__((noinline))
id *autoreleaseSlow(id obj)

    AutoreleasePoolPage *page;
    page = hotPage();

    // The code below assumes some cases are handled by autoreleaseFast()
    assert(!page || page->full());

    if (!page) 
        // No pool. Silently push one.
        assert(obj != POOL_SENTINEL);

        if (DebugMissingPools) 
            _objc_inform("MISSING POOLS: Object %p of class %s "
                         "autoreleased with no pool in place - "
                         "just leaking - break on "
                         "objc_autoreleaseNoPool() to debug", 
                         (void*)obj, object_getClassName(obj));
            objc_autoreleaseNoPool(obj);
            return nil;
        

        push();
        page = hotPage();
    

    do 
        if (page->child) page = page->child;
        else page = new AutoreleasePoolPage(page);
     while (page->full());

    setHotPage(page);
    return page->add(obj);

但之前的版本(532.2,随 OS X 10.8 和 iOS 6 一起提供),does not:

static __attribute__((noinline))
id *autoreleaseSlow(id obj)

    AutoreleasePoolPage *page;
    page = hotPage();

    // The code below assumes some cases are handled by autoreleaseFast()
    assert(!page || page->full());

    if (!page) 
        assert(obj != POOL_SENTINEL);
        _objc_inform("Object %p of class %s autoreleased "
                     "with no pool in place - just leaking - "
                     "break on objc_autoreleaseNoPool() to debug", 
                     obj, object_getClassName(obj));
        objc_autoreleaseNoPool(obj);
        return NULL;
    

    do 
        if (page->child) page = page->child;
        else page = new AutoreleasePoolPage(page);
     while (page->full());

    setHotPage(page);
    return page->add(obj);

请注意,以上内容适用于任何pthreads,而不仅仅是NSThreads。

所以基本上,如果您在 OS X 10.9+ 或 iOS 7+ 上运行,在没有池的线程上自动释放不会导致泄漏。这没有记录在案,并且是内部实现细节,因此请小心依赖它,因为 Apple 可能会在未来的操作系统中更改它。但是,我看不出他们有什么理由删除这个功能,因为它很简单,只有好处,没有缺点,除非他们完全重写自动释放池的工作方式或其他东西。

【讨论】:

出色的答案。伟大的侦探工作。感谢您发布。 (已投票) 请注意,自动清理不会发生在主线程中。清理函数 tls_dealloc 是一个线程本地存储 (TLS) 析构函数,通过调用 pthread_key_init_np 添加。这些析构函数是从pthread_exit 调用的,当线程从其启动例程返回时会隐式调用它,但在主线程的情况下不会。所以autoreleaseNoPage添加的隐式池不会被耗尽。见pubs.opengroup.org/onlinepubs/9699919799/functions/…【参考方案2】:

Apple documentation says(第 4 段):

您使用通常的 alloc 创建一个 NSAutoreleasePool 对象和 初始化消息并用 drain 处理它(或释放 - 理解 区别,请参阅垃圾收集)。因为你不能保留一个 自动释放池(或自动释放它——参见保留和自动释放), 排空一个池最终会产生释放它的效果。你 应始终在同一上下文中耗尽自动释放池 (调用方法或函数,或循环体) 创建的。有关详细信息,请参阅使用自动释放池块。

【讨论】:

以上是关于NSThread 现在会自动创建 autoreleasepool 吗?的主要内容,如果未能解决你的问题,请参考以下文章

多线程NSThread

NSThread

NSThread

简述内存管理

NSThread

Objective-C 中的 NSThread 错误