美文网首页selector
iOS 子线程的自动释放池

iOS 子线程的自动释放池

作者: boy丿log | 来源:发表于2020-06-19 11:14 被阅读0次

    都说子线程不主动获取没有runloop,而每个runloop都会在开始创建一个自动释放池,那没有runloop,子线程是否有自动释放池呢?比如下面这个

    image.png

    那么,

    1. 子线程是否有自动释放池呢
    2. 这个obj究竟在什么时候释放呢?
    3. 是在子线程销毁后释放,还是与子线程的生命周期无关?

    接下来让我们带着疑问一起去看下runtime源码一探究竟吧。

    可编译源码地址

    autoreleasepool源码在NSObject.mm文件中。我们在main.m中添加如下代码,并将main.m编译成main.cpp文件,看下到底发生了什么。

    #import <Foundation/Foundation.h>
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            dispatch_async(dispatch_get_global_queue(0, 0), ^{
                __autoreleasing NSObject *a = [NSObject new];
            });
            while (1) {
                
            }
        }
        return 0;
    }
    
    1. cd 到main.m目录
    2. clang -rewrite-objc -fobjc-arc -stdlib=libc++ -mmacosx-version-min=10.7 -fobjc-runtime=macosx-10.7 -Wno-deprecated-declarations main.m

    这个时候我们会发现在main.m同级目录出现了一个main.cpp文件,打开此文件,移动到文件底部

    image.png

    接下来假设你已经知道了autoreleasePool的相关知识,不知道的可以看一下这里

    首先,在你创建一个autorelease对象的时候,
    会调用对象的autorelease方法,这个方法目前在NSObject.h中是没有声明的,不过我们可以看NSObject.mm方法

    1.调用autorelease
    2.调用到_objc_rootAutorelease
    3.调用obj->rootAutorelease

    。。。

    最终,会调用到autoreleaseFast方法:

    这里我们可以注意下hotPage()方法

    这里的获取的autoreleasePoolPage是调用的tls_get_direct方法去获取

    static inline void *tls_get_direct(tls_key_t k)
    { 
        ASSERT(is_valid_direct_key(k));
    
        if (_pthread_has_direct_tsd()) {
            return _pthread_getspecific_direct(k);
        } else {
            return pthread_getspecific(k);
        }
    }
    static inline void tls_set_direct(tls_key_t k, void *value) 
    { 
        ASSERT(is_valid_direct_key(k));
    
        if (_pthread_has_direct_tsd()) {
            _pthread_setspecific_direct(k, value);
        } else {
            pthread_setspecific(k, value);
        }
    }
    

    这两个函数相当于NSObject的动态关联对象,可以将数据绑定到线程上。那么很明显,这个时候子线程是不可能有hotpage,所以这个时候会走到autoreleaseNoPage方法,

        id *autoreleaseNoPage(id obj)
        {
            // "No page" could mean no pool has been pushed
            // or an empty placeholder pool has been pushed and has no contents yet
            ASSERT(!hotPage());
    
            bool pushExtraBoundary = false;
            if (haveEmptyPoolPlaceholder()) {
                // We are pushing a second pool over the empty placeholder pool
                // or pushing the first object into the empty placeholder pool.
                // Before doing that, push a pool boundary on behalf of the pool 
                // that is currently represented by the empty placeholder.
                pushExtraBoundary = true;
            }
            else if (obj != POOL_BOUNDARY  &&  DebugMissingPools) {
                // We are pushing an object with no pool in place, 
                // and no-pool debugging was requested by environment.
                _objc_inform("MISSING POOLS: (%p) Object %p of class %s "
                             "autoreleased with no pool in place - "
                             "just leaking - break on "
                             "objc_autoreleaseNoPool() to debug", 
                             objc_thread_self(), (void*)obj, object_getClassName(obj));
                objc_autoreleaseNoPool(obj);
                return nil;
            }
            else if (obj == POOL_BOUNDARY  &&  !DebugPoolAllocation) {
                // We are pushing a pool with no pool in place,
                // and alloc-per-pool debugging was not requested.
                // Install and return the empty pool placeholder.
                return setEmptyPoolPlaceholder();
            }
    
            // We are pushing an object or a non-placeholder'd pool.
    
            // Install the first page.
            AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
            setHotPage(page);
            
            // Push a boundary on behalf of the previously-placeholder'd pool.
            if (pushExtraBoundary) {
                page->add(POOL_BOUNDARY);
            }
            
            // Push the requested object or pool.
            return page->add(obj);
        }
    
    

    通过一系列的判断,就会创建一个自动释放池。

    所以,第一个问题我们解决了,那就是,在子线程中原本是没有自动释放池的,但是如果有runloop或者autorelease对象的时候,就会自动的创建自动释放池。

    那么自动释放池又是什么时候做pop操作的呢?

    在autoreleasepool的init方法中我们能看到一些端倪

    每个autorelease创建的时候都会监听当前线程的销毁方法,在线程退出时调用tls_dealloc方法。

    static void tls_dealloc(void *p) 
        {
            if (p == (void*)EMPTY_POOL_PLACEHOLDER) {
                // No objects or pool pages to clean up here.
                return;
            }
    
            // reinstate TLS value while we work
            setHotPage((AutoreleasePoolPage *)p);
    
            if (AutoreleasePoolPage *page = coldPage()) {
                if (!page->empty()) objc_autoreleasePoolPop(page->begin());  // pop all of the pools
                if (slowpath(DebugMissingPools || DebugPoolAllocation)) {
                    // pop() killed the pages already
                } else {
                    page->kill();  // free all of the pages
                }
            }
            
            // clear TLS value so TLS destruction doesn't loop
            setHotPage(nil);
        }
    

    然后这个tls_dealloc会调用autoreleasePoolPop清楚所有autorelease对象。

    相关文章

      网友评论

        本文标题:iOS 子线程的自动释放池

        本文链接:https://www.haomeiwen.com/subject/vbfvxktx.html