美文网首页iOS-Autorelease Pool
自动释放池 & Runloop

自动释放池 & Runloop

作者: 深圳_你要的昵称 | 来源:发表于2020-12-03 09:23 被阅读0次

    前言

    本篇文章会大致分析下自动释放池(AutoreleasePool)Runloop的底层实现原理,这两个知识点也是面试中经常问到的,希望大家都能掌握这些内容,同时,有不对的地方,希望大家及时指正,谢谢~

    一、自动释放池

    AutoreleasePool是OC中的一种内存自动回收机制,它可以将加入AutoreleasePool中的变量release的时机延迟,简单来说,就是当创建一个对象,正常情况下,变量会在其作用域的结束时会立即release释放。如果将该对象加入到了AutoreleasePool中,即使作用域结束了,这个对象并不会立即释放,它会等到runloop休眠或超出autoreleasepool作用域{}之后才会被释放。其机制如下图所示👇

    1. 从程序App启动dyld加载完成后,主线程对应的runloop会处于休眠状态,等待用户交互唤醒runloop;
    2. 用户的每一次交互都会启动一次runloop,用于处理用户的所有点击、触摸事件等;
    3. CocoaTouch在监听到交互事件后,就会创建自动释放池,并将所有延迟释放的对象添加到自动释放池中;
    4. 在一次完整runloop结束之前CocoaTouch会向自动释放池中所有对象发送release消息,然后销毁自动释放池。

    1.1 找入口

    那AutoreleasePool对应的底层到底是什么东西呢?首先需要找到入口,我们先clang一下看看@autoreleasepool对应的C++代码是什么👇

    int main(int argc, char * argv[]) {
        @autoreleasepool {
            NSLog(@"来了,老弟");
    
            return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));;
        }
    }
    

    xcrun -sdk iphonesimulator clang -arch x86_64 -rewrite-objc main.m

    我们发现,@autoreleasepool对应的底层结构是__AtAutoreleasePool,我们继续在.cpp中搜索__AtAutoreleasePool👇

    struct __AtAutoreleasePool {
      __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
      ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
      void * atautoreleasepoolobj;
    };
    

    可见,__AtAutoreleasePool是一个结构体,包含构造函数析构函数和一个指针atautoreleasepoolobj
    接下来,我们再打断点👇

    看看汇编代码👇

    定位到了objc_autoreleasePoolPushobjc_autoreleasePoolPop。这两个函数就是@autoreleasepool的作用域{}对应底层的压栈出栈

    1.2 底层解析

    通过上面对AutoreleasePool入口的定位,我们知道了objc_autoreleasePoolPushobjc_autoreleasePoolPop,现在我们去到Objc源码,首先搜索objc_autoreleasePoolPush,看看👇

    定位到是类AutoreleasePoolPage,再搜索这个类👇

    通过注释,有以下几点说明

    1. 自动释放池是一个指针集合,指向一个结构空间。
    2. 指针集合中的指针是指向要被释放的对象或者pool_boundary(之前叫做哨兵,现在经常被称为边界
    3. 自动释放池是一个的结构 ,而且这个是一个双向链表(含有父节点 和 子节点)
    4. 自动释放池和线程有很大的关系

    那么,问题来了👇

    1. 自动释放池是什么时候创建的?
    2. 对象是如何被加入到自动释放池的?
    3. 哪些对象会被加入到自动释放池?

    带着这些问题,我们进一步看看自动释放池底层原理

    1.2.1 AutoreleasePoolPage

    至此,我们知道了自动释放池是一个的结构,就是AutoreleasePoolPage。大致结构分布如下:

    //************宏定义************
    #define PAGE_MIN_SIZE           PAGE_SIZE
    #define PAGE_SIZE               I386_PGBYTES
    #define I386_PGBYTES            4096            /* bytes per 80386 page */
    
    //************类定义************
    class AutoreleasePoolPage : private AutoreleasePoolPageData
    {
        friend struct thread_data_t;
    
    public:
        //页的大小
        static size_t const SIZE =
    #if PROTECT_AUTORELEASEPOOL
            PAGE_MAX_SIZE;  // must be multiple of vm page size
    #else
            PAGE_MIN_SIZE;  // size and alignment, power of 2
    #endif
    
    private:
        
        ...
        
        //构造函数
        AutoreleasePoolPage(AutoreleasePoolPage *newParent) :
            AutoreleasePoolPageData(begin(),//开始存储的位置
                                    objc_thread_self(),//传的是当前线程,当前线程时通过tls获取的
                                    newParent,
                                    newParent ? 1+newParent->depth : 0,//如果是第一页深度为0,往后是前一个的深度+1
                                    newParent ? newParent->hiwat : 0)
        {...}
        
        //析构函数
        ~AutoreleasePoolPage() {...}
        
        ...
        
        //页的开始位置
        id * begin() {...}
        
        //页的结束位置
        id * end() {...}
       
        //页是否为空
        bool empty() {...}
        
        //页是否满了
        bool full() {...}
       
        //页的存储是否少于一半
        bool lessThanHalfFull() {...}
         
         //添加释放对象
        id *add(id obj){...}
        
        //释放所有对象
        void releaseAll() {...}
        
        //释放到stop位置之前的所有对象
        void releaseUntil(id *stop) {...}
        
        //杀掉
        void kill() {...}
        
        //释放本地线程存储空间
        static void tls_dealloc(void *p) {...}
        
        //获取AutoreleasePoolPage
        static AutoreleasePoolPage *pageForPointer(const void *p) {...}
        static AutoreleasePoolPage *pageForPointer(uintptr_t p)  {...}
        
        //是否有空池占位符
        static inline bool haveEmptyPoolPlaceholder() {...}
        
        //设置空池占位符
        static inline id* setEmptyPoolPlaceholder(){...}
        
        //获取当前操作页
        static inline AutoreleasePoolPage *hotPage(){...}
        
        //设置当前操作页
        static inline void setHotPage(AutoreleasePoolPage *page) {...}
        
        //获取coldPage
        static inline AutoreleasePoolPage *coldPage() {...}
        
        //快速释放
        static inline id *autoreleaseFast(id obj){...}
       
       //添加自动释放对象,当页满的时候调用这个方法
        static __attribute__((noinline))
        id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page) {...}
        
        //添加自动释放对象,当没页的时候使用这个方法
        static __attribute__((noinline))
        id *autoreleaseNoPage(id obj){...}
       
       //创建新页
        static __attribute__((noinline))
        id *autoreleaseNewPage(id obj) {...}
        
    public:
        //自动释放
        static inline id autorelease(id obj){...}
       
        //入栈
        static inline void *push() {...}
        
        //兼容老的 SDK 出栈方法
        __attribute__((noinline, cold))
        static void badPop(void *token){...}
        
        //出栈页面
        template<bool allowDebug>
        static void
        popPage(void *token, AutoreleasePoolPage *page, id *stop){...}
        __attribute__((noinline, cold))
        static void
        popPageDebug(void *token, AutoreleasePoolPage *page, id *stop){...}
        
        //出栈
        static inline void
        pop(void *token){...}
        
        static void init(){...}
        
        //打印
        __attribute__((noinline, cold))
        void print(){...}
        
        //打印所有
        __attribute__((noinline, cold))
        static void printAll(){...}
        
        //打印Hiwat
        __attribute__((noinline, cold))
        static void printHiwat(){...}
    

    它的父类是AutoreleasePoolPageData👇

    class AutoreleasePoolPage;
    struct AutoreleasePoolPageData
    {
        //用来校验AutoreleasePoolPage的结构是否完整
        magic_t const magic;//16个字节
        //指向最新添加的autoreleased对象的下一个位置,初始化时指向begin()
        __unsafe_unretained id *next;//8字节
        //指向当前线程
        pthread_t const thread;//8字节
        //指向父节点,第一个结点的parent值为nil
        AutoreleasePoolPage * const parent;//8字节
        //指向子节点,最后一个结点的child值为nil
        AutoreleasePoolPage *child;//8字节
        //表示深度,从0开始,往后递增1
        uint32_t const depth;//4字节
        //表示high water mark 最大入栈数量标记
        uint32_t hiwat;//4字节
    
        //默认构造函数
        AutoreleasePoolPageData(__unsafe_unretained id* _next, pthread_t _thread, AutoreleasePoolPage* _parent, uint32_t _depth, uint32_t _hiwat)
            : magic(), next(_next), thread(_thread),
              parent(_parent), child(nil),
              depth(_depth), hiwat(_hiwat)
        {
        }
    };
    

    这里发现, AutoreleasePoolPage同时也是一个双向链表结构 --> 包含parentchild。再看看AutoreleasePoolPageData结构体所占内存大小,算出来是56字节

    1.2.2 objc_autoreleasePoolPush

    接下来我们看看压栈操作objc_autoreleasePoolPush的源码👇

    void *
    objc_autoreleasePoolPush(void)
    {
        return AutoreleasePoolPage::push();
    }
    

    还是回到类AutoreleasePoolPagepush函数👇

        static inline void *push() 
        {
            id *dest;
            if (slowpath(DebugPoolAllocation)) {
                // Each autorelease pool starts on a new pool page.
                dest = autoreleaseNewPage(POOL_BOUNDARY);
            } else {
                dest = autoreleaseFast(POOL_BOUNDARY);
            }
            ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
            return dest;
        }
    

    流程不难,大致分为以下几步:

    1. 判断当前是否有池,
    2. 若没有,则创建 -->autoreleaseNewPage
    3. 有池,则加入一个边界对象POOL_BOUNDARY
    4. 返回池的边界大小
    autoreleaseNewPage

    接下来我们仔细看看创建池的底层流程👇

        static __attribute__((noinline))
        id *autoreleaseNewPage(id obj)
        {
            AutoreleasePoolPage *page = hotPage();
            if (page) return autoreleaseFullPage(obj, page);
            else return autoreleaseNoPage(obj);
        }
    

    根据hotPage(),生成autoreleaseFullPage,否则就返回autoreleaseNoPage

    hotPage

    继续,我们先看看hotPage()源码👇

        static inline AutoreleasePoolPage *hotPage() 
        {
            AutoreleasePoolPage *result = (AutoreleasePoolPage *)
                tls_get_direct(key);
            if ((id *)result == EMPTY_POOL_PLACEHOLDER) return nil;
            if (result) result->fastcheck();
            return result;
        }
    

    然后看看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);
        }
    }
    

    源码可知,是在当前线程pthead中,根据key获取的页AutoreleasePoolPage,其中key就是自动释放池key👇

    static pthread_key_t const key = AUTORELEASE_POOL_KEY;

    由此可见,当前线程pthead指向的空间中,有一个类似字典结构,从这里根据AUTORELEASE_POOL_KEY能获取到自动释放池。

    autoreleaseFullPage

    接下来,看看autoreleaseFullPage源码👇

        static __attribute__((noinline))
        id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
        {
            // The hot page is full. 
            // Step to the next non-full page, adding a new page if necessary.
            // Then add the object to that page.
            ASSERT(page == hotPage());
            ASSERT(page->full()  ||  DebugPoolAllocation);
    
            do {
                if (page->child) page = page->child;
                else page = new AutoreleasePoolPage(page);
            } while (page->full());
    
            setHotPage(page);
            return page->add(obj);
        }
    

    然后add的源码

        id *add(id obj)
        {
            ASSERT(!full());
            unprotect();
            id *ret = next;  // faster than `return next-1` because of aliasing
            *next++ = obj;
            protect();
            return ret;
        }
    

    流程也很简单,核心代码是do-while循环:寻找当前池页的子页,没有则创建新的page,直到达到Size容量,然后将这个子页设置为hotPage,即线程pThread中的 AUTORELEASE_POOL_KEY所对应的value是这个子页page,最后将压栈的对象objcadd压栈到这个子页page中,实际是对当前page的next++

    autoreleaseNoPage

    objc_autoreleasePoolPush中,如果当前线程pThread中的AUTORELEASE_POOL_KEY所对应的value没有值时,则会进入autoreleaseNoPage流程👇

    其中,我们发现,autoreleaseNoPage中会调用AutoreleasePoolPage的构造函数,创建一个新的,构造函数源码如下👇

        AutoreleasePoolPage(AutoreleasePoolPage *newParent) :
            AutoreleasePoolPageData(begin(),
                                    objc_thread_self(),
                                    newParent,
                                    newParent ? 1+newParent->depth : 0,
                                    newParent ? newParent->hiwat : 0)
        { 
            if (parent) {
                parent->check();
                ASSERT(!parent->child);
                parent->unprotect();
                parent->child = this;
                parent->protect();
            }
            protect();
        }
    

    其中,newParent的值是nil,而父类结构体的构造方法👇

        AutoreleasePoolPageData(__unsafe_unretained id* _next, pthread_t _thread, AutoreleasePoolPage* _parent, uint32_t _depth, uint32_t _hiwat)
            : magic(), next(_next), thread(_thread),
              parent(_parent), child(nil),
              depth(_depth), hiwat(_hiwat)
        {
        }
    

    那么,_next就是begin()压栈开始位置_thread就是当前线程objc_thread_self()

    id * begin() {        
    //等于 首地址+56(AutoreleasePoolPage类所占内存大小)
       return (id *) ((uint8_t *)this+sizeof(*this));
    }
    
    __attribute__((const))
    static inline pthread_t objc_thread_self()
    {
        //通过tls获取当前线程
        return (pthread_t)tls_get_direct(_PTHREAD_TSD_SLOT_PTHREAD_SELF);
    }
    
    autoreleaseFast

    以上分析了autoreleaseNewPage,接下来就是else情况autoreleaseFast了,源码👇

        static inline id *autoreleaseFast(id obj)
        {
            AutoreleasePoolPage *page = hotPage();
            if (page && !page->full()) {
                return page->add(obj);
            } else if (page) {
                return autoreleaseFullPage(obj, page);
            } else {
                return autoreleaseNoPage(obj);
            }
        }
    

    分析完autoreleaseFullPageautoreleaseNoPage之后,现在回头来看autoreleaseFast就很简单了,大致流程👇:

    1. 获取当前线程中AUTORELEASE_POOL_KEY对应的
    2. 如果该存在,且有空间,则压栈objc
    3. 如果没有空间,则扩容,加一页,再压栈objc
    4. 如果该不存在,则创建一页,其key设为AUTORELEASE_POOL_KEY,最后压栈objc

    小结
    综上所述,objc_autoreleasePoolPush压栈的核心流程如下👇

    1. 当没有pool,即只有空占位符(存储在线程tls中)时,则创建页,压栈边界对象
    2. 中压栈普通对象,通过next++进行Page容量的标记
    3. 容量满了,就去子节点页压栈
    4. 如果当前池中所有都满了,那么新创建一个,压栈边界对象,再压栈对象obj
    附:打印查看AutoreleasePool的内存结构

    在ARC下,是无法手动调用autorelease的,所以将Demo切换至MRC模式,修改工程配置BuildSetting,搜索automatic refer改为NO,如下图👇

    再看示例代码👇

    //************打印自动释放池结构************
    extern void _objc_autoreleasePoolPrint(void);
    
    //************运行代码************
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            //循环创建对象,并加入自动释放池
            for (int i = 0; i < 5; i++) {
                 NSObject *objc = [[NSObject alloc] autorelease];
            }
            //调用
            _objc_autoreleasePoolPrint();
        }
    }
    

    运行👇

    上图红框处就是自动释放池的打印信息,其中POOL就是池的边界。再看池的首地址0x7f9ad8809000边界地址0x7f9ad8809038,相差38(16进制),转换成十进制56,正好验证了之前说的AutoreleasePool类所占内存空间的大小。

    接着,我们将for循环改为505次,run👇

    可以看到,第2页存储了一个,那么第一页存储了504个对象+1个边界对象,共计505个。不信,再将循环次数改为505+505,run👇

    第3页是1个,共计是505+505+1个边界对象,那么一页最多可以存储505个对象

    以上,我们可以得出以下结论:

    1. 边界对象在第1页,第1页最多可存储504个对象,第1页满了,会开辟新的一页
    2. 从第2页开始,最多可存储505个对象
    3. 一页中所有对象所占内存是:505 * 8字节(因为是指针) = 4040字节

    再回头看看类AutoreleasePool,内部定义了一个SIZE👇


    其中

    #define PAGE_MIN_SHIFT          12
    #define PAGE_MIN_SIZE           (1 << PAGE_MIN_SHIFT)
    

    1<<12就是2的12次方=4096,之前说过,边界对象距离池首地址相差56,4040+56 = 4096,完美契合!

    AutoreleasePool的内部结构图如下👇

    附面试题:

    边界对象在一个自动释放池有几个?

    只有一个边界对象对象,且在第一页
    第一页最多可以存504个对象,第二页开始最多存 505个

    autorelease底层分析

    MRC中,调用[obj autorelease]来延迟内存的释放是一件简单自然的事,ARC下,我们甚至可以完全不知道Autorelease就能管理好内存。而在这背后,objc和编译器都帮我们做了哪些事呢,它们是如何协作来正确管理内存的呢?
    我们还是一样找入口。打断点,看汇编👇

    定位到objc_autorelease,查看源码👇

    __attribute__((aligned(16), flatten, noinline))
    id
    objc_autorelease(id obj)
    {
        if (!obj) return obj;
        if (obj->isTaggedPointer()) return obj;
        return obj->autorelease();
    }
    

    obj为nil或为isTaggedPointer小对象时,直接返回obj,否则进入autorelease()👇

    inline id 
    objc_object::autorelease()
    {
        ASSERT(!isTaggedPointer());
        if (fastpath(!ISA()->hasCustomRR())) {
            return rootAutorelease();
        }
    
        return ((id(*)(objc_object *, SEL))objc_msgSend)(this, @selector(autorelease));
    }
    

    其中,ISA()->hasCustomRR()源码👇

    bool hasCustomRR() const {
        return !bits.getBit(FAST_HAS_DEFAULT_RR);
    }
    // 其中FAST_HAS_DEFAULT_RR👇
    // class or superclass has default retain/release/autorelease/retainCount/
    //   _tryRetain/_isDeallocating/retainWeakReference/allowsWeakReference
    #define FAST_HAS_DEFAULT_RR     (1UL<<2)
    

    那么autorelease()流程如下:

    1. ISA()->hasCustomRR() --> 判断当前类或者父类含有默认的retain/release/autorelease/retainCount/_tryRetain/_isDeallocating/retainWeakReference/allowsWeakReference方法。
    2. 有,则直接消息发送,调用autorelease方法
    3. 没有,则调用rootAutorelease(),源码👇
    inline id 
    objc_object::rootAutorelease()
    {
        if (isTaggedPointer()) return (id)this;
        if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;
    
        return rootAutorelease2();
    }
    
    __attribute__((noinline,used))
    id 
    objc_object::rootAutorelease2()
    {
        ASSERT(!isTaggedPointer());
        return AutoreleasePoolPage::autorelease((id)this);
    }
    
        static inline id autorelease(id obj)
        {
            ASSERT(obj);
            ASSERT(!obj->isTaggedPointer());
            id *dest __unused = autoreleaseFast(obj);
            ASSERT(!dest  ||  dest == EMPTY_POOL_PLACEHOLDER  ||  *dest == obj);
            return obj;
        }
    

    以上,小对象一律不看,最终来到了autorelease(id obj),且是调用autoreleaseFast压栈obj。

    注意,从ASSERT(!dest || dest == EMPTY_POOL_PLACEHOLDER || *dest == obj);这句代码可以看出,dest既可以是边界对象,也可以是普通对象,所以autorelease没有区分边界对象的。

    1.2.3 objc_autoreleasePoolPop

    那么,接下来,我们最后来看看出栈objc_autoreleasePoolPop的底层流程👇

    void
    objc_autoreleasePoolPop(void *ctxt)
    {
        AutoreleasePoolPage::pop(ctxt);
    }
    

    和push一样,进入到类AutoreleasePoolPage的pop函数👇

    接着,我们看看popPage👇

    releaseUntil

    我们先看看重点函数releaseUntil👇

    上图可知,外层while循环,其实是在遍历你要释放的obj之前的对象,然后逐一进行释放,同时将其对应的内存状态标识为SCRIBBLE(释放后的状态)。

    kill

    最后看看kill👇

    小结
    综上所述,objc_autoreleasePoolPop出栈的核心流程如下👇

    1. releaseUntil,释放当前页出栈对象obj+obj之前的对象,之前的页
    2. 也是通过next--标记当前页的容量
    3. 出栈后,判断当前页的容量是否少于505/2(一半容量)则删除子节点页面,不是则删除孙子节点页面

    二、RunLoop

    RunLoop也是近年来面试必问的知识点,而且基本涉及很细很底层,所以本篇也在此探索一些底层实现原理。我们对于RunLoop,最关心的几个问题无非是以下:

    1. runloop是什么?
    2. runloop和线程的关系?
    3. runloop是什么时候创建的?

    2.1 RunLoop简介

    RunLoop事件接收和分发机制的一个实现,是线程相关的基础框架的一部分,一个RunLoop就是一个事件处理的循环,用来不停的调度工作以及处理输入事件。RunLoop本质是一个do-while循环,没事做就休息,来活了就干活。与普通的while循环的区别👇

    • 普通的while循环会导致CPU进入忙等待状态,即一直消耗cpu
    • RunLoop则不会,RunLoop是一种闲等待,即它具备休眠功能
    RunLoop的作用
    1. 保持程序的持续运行
    2. 处理App中的各种事件(触摸、定时器、performSelector)
    3. 节省cpu资源,提供程序的性能,该做事就做事,该休息就休息
    Runloop源码

    RunLoop源码的下载地址,在其中找到最新版下载即可。

    2.2 RunLoop与线程的关系

    RunLoop与线程的关系得从RunLoop的获取方式这个切入点查看。系统给了两种方式获取RunLoop👇

    // 主运行循环
     CFRunLoopRef mainRunloop = CFRunLoopGetMain();
     // 当前运行循环
     CFRunLoopRef currentRunloop = CFRunLoopGetCurrent();
    

    2.2.1 CFRunLoopGetMain

    CFRunLoopRef CFRunLoopGetMain(void) {
        CHECK_FOR_FORK();
        static CFRunLoopRef __main = NULL; // no retain needed
        //pthread_main_thread_np 主线程
        if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed
        return __main;
    }
    
    // -----------------------------分割线 -----------------------------
    
    CFRunLoopRef CFRunLoopGetCurrent(void) {
        CHECK_FOR_FORK();
        CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
        if (rl) return rl;
        return _CFRunLoopGet0(pthread_self());
    }
    

    接着看看_CFRunLoopGet0👇

    // should only be called by Foundation
    // t==0 is a synonym for "main thread" that always works
    CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
        //如果t不存在,则标记为主线程(即默认情况,默认是主线程)
        if (pthread_equal(t, kNilPthreadT)) {
            t = pthread_main_thread_np();
        }
        __CFSpinLock(&loopsLock);
        if (!__CFRunLoops) {
            __CFSpinUnlock(&loopsLock);
            
            //创建全局字典,标记为kCFAllocatorSystemDefault
            CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
            //通过主线程 创建主运行循环
            CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
            //利用dict,进行key-value绑定操作,即可以说明,线程和runloop是一一对应的
            // dict : key value
            CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
            
            if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
                CFRelease(dict);
            }
            
            CFRelease(mainLoop);
            __CFSpinLock(&loopsLock);
        }
        //通过其他线程获取runloop
        CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
        __CFSpinUnlock(&loopsLock);
        if (!loop) {
            //如果没有获取到,则新建一个运行循环
            CFRunLoopRef newLoop = __CFRunLoopCreate(t);
            __CFSpinLock(&loopsLock);
            loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
            if (!loop) {
                //将新建的runloop 与 线程进行key-value绑定
                CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
                loop = newLoop;
            }
            // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
            __CFSpinUnlock(&loopsLock);
            CFRelease(newLoop);
        }
        if (pthread_equal(t, pthread_self())) {
            _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
            if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
                _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
            }
        }
        return loop;
    }
    

    以上可以看出,RunLoop只有两种:一种是主线程的, 一个是其他线程的。即runloop和线程是一一对应的。

    2.3 RunLoop的使用

    _CFRunLoopGet0的流程中,有一个创建步骤__CFRunLoopCreate,我们先看看其源码。

    2.3.1 创建RunLoop

    static CFRunLoopRef __CFRunLoopCreate(pthread_t t) {
        CFRunLoopRef loop = NULL;
        CFRunLoopModeRef rlm;
        uint32_t size = sizeof(struct __CFRunLoop) - sizeof(CFRuntimeBase);
        loop = (CFRunLoopRef)_CFRuntimeCreateInstance(kCFAllocatorSystemDefault, __kCFRunLoopTypeID, size, NULL);
        //如果loop为空,则直接返回NULL
        if (NULL == loop) {
            return NULL;
        }
        //runloop属性配置
        (void)__CFRunLoopPushPerRunData(loop);
        __CFRunLoopLockInit(&loop->_lock);
        loop->_wakeUpPort = __CFPortAllocate();
        if (CFPORT_NULL == loop->_wakeUpPort) HALT;
        __CFRunLoopSetIgnoreWakeUps(loop);
        loop->_commonModes = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
        CFSetAddValue(loop->_commonModes, kCFRunLoopDefaultMode);
        loop->_commonModeItems = NULL;
        loop->_currentMode = NULL;
        loop->_modes = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
        loop->_blocks_head = NULL;
        loop->_blocks_tail = NULL;
        loop->_counterpart = NULL;
        loop->_pthread = t;
    #if DEPLOYMENT_TARGET_WINDOWS
        loop->_winthread = GetCurrentThreadId();
    #else
        loop->_winthread = 0;
    #endif
        rlm = __CFRunLoopFindMode(loop, kCFRunLoopDefaultMode, true);
        if (NULL != rlm) __CFRunLoopModeUnlock(rlm);
        return loop;
    }
    

    这个创建,和我们平时写的初始化代码基本一样,我们再看看返回的对象是CFRunLoopRef类型👇

    typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoop * CFRunLoopRef;
    

    接着看__CFRunLoop👇

    struct __CFRunLoop {
        CFRuntimeBase _base;
        pthread_mutex_t _lock;            /* locked for accessing mode list */
        __CFPort _wakeUpPort;            // used for CFRunLoopWakeUp
        Boolean _unused;
        volatile _per_run_data *_perRunData;              // reset for runs of the run loop
        pthread_t _pthread;
        uint32_t _winthread;
        CFMutableSetRef _commonModes;
        CFMutableSetRef _commonModeItems;
        CFRunLoopModeRef _currentMode;
        CFMutableSetRef _modes;
        struct _block_item *_blocks_head;
        struct _block_item *_blocks_tail;
        CFTypeRef _counterpart;
    };
    

    从结构体__CFRunLoop中可以得出,一个RunLoop依赖于多个Mode(_commonModes),意味着一个RunLoop需要处理多个事务_commonModeItems,那么一个Mode对应多个Item,而一个item中,包含了timer、source、observer,如下所示👇

    Mode类型

    其中mode在苹果文档中提及的有五个,而在iOS中公开暴露出来的只有 NSDefaultRunLoopModeNSRunLoopCommonModes。 而NSRunLoopCommonModes实际上是一个 Mode 的集合,默认包括 NSDefaultRunLoopModeNSEventTrackingRunLoopMode

    1. NSDefaultRunLoopMode:默认的mode,正常情况下都是在这个mode
    2. NSConnectionReplyMode
    3. NSModalPanelRunLoopMode
    4. NSEventTrackingRunLoopMode:使用这个Mode去跟踪来自用户交互的事件(比如UITableView上下滑动)
    5. NSRunLoopCommonModes:伪模式,灵活性更好
    Source & Timer & Observer

    Source表示可以唤醒RunLoop的一些事件,例如用户点击了屏幕,就会创建一个RunLoop,主要分为Source0和Source1:

    • Source0 表示 非系统事件,即用户自定义的事
    • Source1 表示系统事件,主要负责底层的通讯,具备唤醒能力

    Timer 就是常用NSTimer定时器这一类

    Observer 主要用于监听RunLoop的状态变化,并作出一定响应,主要有以下一些状态👇

    typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
        //进入RunLoop
        kCFRunLoopEntry = (1UL << 0),
        //即将处理Timers
        kCFRunLoopBeforeTimers = (1UL << 1),
        //即将处理Source
        kCFRunLoopBeforeSources = (1UL << 2),
        //即将进入休眠
        kCFRunLoopBeforeWaiting = (1UL << 5),
        //被唤醒
        kCFRunLoopAfterWaiting = (1UL << 6),
        //退出RunLoop
        kCFRunLoopExit = (1UL << 7),
        kCFRunLoopAllActivities = 0x0FFFFFFFU
    };
    
    示例验证:RunLoop和mode是一对多关系

    通过lldb命令获取mainRunloop、currentRunloop的currentMode

    由此可见,runloop在运行时的mode只有一个

    获取mainRunloop的所有模型,即po CFRunLoopCopyAllModes(mainRunloop)

    由此可见,runloopCFRunloopMode一对多的关系

    示例验证:mode和Item也是一对多关系

    在RunLoop源码中查看Item类型,有以下几种👇

    1. block应用:__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__
    2. 调用timer:__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__
    3. 响应source0: __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__
    4. 响应source1: __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__
    5. GCD主队列:__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__
    6. observer源: __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__

    在这里以Timer为例,一般初始化timer时,都会将timer通过addTimer:forMode:方法添加到Runloop中,于是在源码中查找addTimer的相关方法,即CFRunLoopAddTimer方法,其源码实现如下

    void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef rlt, CFStringRef modeName) {
        CHECK_FOR_FORK();
        if (__CFRunLoopIsDeallocating(rl)) return;
        if (!__CFIsValid(rlt) || (NULL != rlt->_runLoop && rlt->_runLoop != rl)) return;
        __CFRunLoopLock(rl);
        
        // 重点 : kCFRunLoopCommonModes
        if (modeName == kCFRunLoopCommonModes) {
            //如果是kCFRunLoopCommonModes 类型
           
            CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
            
            if (NULL == rl->_commonModeItems) {
                rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
            }
            //runloop与mode 是一对多的, mode与item也是一对多的
            CFSetAddValue(rl->_commonModeItems, rlt);
            if (NULL != set) {
                CFTypeRef context[2] = {rl, rlt};
                /* add new item to all common-modes */
                //执行
                CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
                CFRelease(set);
            }
        } else {
            //如果是非commonMode类型
            //查找runloop的模型
            CFRunLoopModeRef rlm = __CFRunLoopFindMode(rl, modeName, true);
            if (NULL != rlm) {
                if (NULL == rlm->_timers) {
                    CFArrayCallBacks cb = kCFTypeArrayCallBacks;
                    cb.equal = NULL;
                    rlm->_timers = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &cb);
                }
            }
            //判断mode是否匹配
            if (NULL != rlm && !CFSetContainsValue(rlt->_rlModes, rlm->_name)) {
                __CFRunLoopTimerLock(rlt);
                if (NULL == rlt->_runLoop) {
                    rlt->_runLoop = rl;
                } else if (rl != rlt->_runLoop) {
                    __CFRunLoopTimerUnlock(rlt);
                    __CFRunLoopModeUnlock(rlm);
                    __CFRunLoopUnlock(rl);
                    return;
                }
                // 如果匹配,则将runloop加进去,而runloop的执行依赖于  [runloop run]
                CFSetAddValue(rlt->_rlModes, rlm->_name);
                __CFRunLoopTimerUnlock(rlt);
                __CFRunLoopTimerFireTSRLock();
                __CFRepositionTimerInMode(rlm, rlt, false);
                __CFRunLoopTimerFireTSRUnlock();
                if (!_CFExecutableLinkedOnOrAfter(CFSystemVersionLion)) {
                    // Normally we don't do this on behalf of clients, but for
                    // backwards compatibility due to the change in timer handling...
                    if (rl != CFRunLoopGetCurrent()) CFRunLoopWakeUp(rl);
                }
            }
            if (NULL != rlm) {
                __CFRunLoopModeUnlock(rlm);
            }
        }
       
        __CFRunLoopUnlock(rl);
    }
    

    其实现主要判断kCFRunLoopCommonModes,然后查找runloop的mode进行匹配处理:

    • 其中kCFRunLoopCommonModes不是一种模式,是一种抽象的伪模式,比defaultMode更加灵活
    • 通过CFSetAddValue(rl->_commonModeItems, rlt);可以得知,runloop与mode 是一对多的,同时可以得出mode 与 item 也是一对多。

    2.3.2 执行RunLoop

    RunLoop的执行依赖于run方法,从下面的堆栈信息中可以看出,其底层执行的是__CFRunLoopRun方法👇


    其源码如下👇

    /* rl, rlm are locked on entrance and exit */
    static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
        ...
        
        do{
            ...
             //通知 Observers: 即将处理timer事件
            if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
            //通知 Observers: 即将处理Source事件
            if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
            //处理Blocks
            __CFRunLoopDoBlocks(rl, rlm);
            //处理sources0
            Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
            //处理sources0返回为YES
            if (sourceHandledThisLoop) {
                // 处理Blocks
                __CFRunLoopDoBlocks(rl, rlm);
            }
            
            ...
            
            //如果是timer
            else if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
                CFRUNLOOP_WAKEUP_FOR_TIMER();
                if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
                    // Re-arm the next timer, because we apparently fired early
                    __CFArmNextTimerInMode(rlm, rl);
                }
            }
            
            ...
            
            //如果是source1
            CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
            if (rls) {
    #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
                mach_msg_header_t *reply = NULL;
                sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
                if (NULL != reply) {
                    (void)mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);
                    CFAllocatorDeallocate(kCFAllocatorSystemDefault, reply);
                }
    #elif DEPLOYMENT_TARGET_WINDOWS
                sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls) || sourceHandledThisLoop;
    #endif
            }
            ...
        
        }while (0 == retVal);
        
        ...
    }
    

    这是省略了其它模式的处理,只看timer的模式,其实进入__CFRunLoopRun源码,针对不同的对象,有不同的处理

    • 如果有observer,则调用 __CFRunLoopDoObservers
    • 如果有block,则调用__CFRunLoopDoBlocks
    • 如果有timer,则调用 __CFRunLoopDoTimers
    • 如果是source0,则调用__CFRunLoopDoSources0
    • 如果是source1,则调用__CFRunLoopDoSource1

    接着我们看看__CFRunLoopDoTimers👇

    static Boolean __CFRunLoopDoTimers(CFRunLoopRef rl, CFRunLoopModeRef rlm, uint64_t limitTSR) {    /* DOES CALLOUT */
        ...
        //循环遍历,做下层单个timer的执行
        for (CFIndex idx = 0, cnt = timers ? CFArrayGetCount(timers) : 0; idx < cnt; idx++) {
            CFRunLoopTimerRef rlt = (CFRunLoopTimerRef)CFArrayGetValueAtIndex(timers, idx);
            Boolean did = __CFRunLoopDoTimer(rl, rlm, rlt);
            timerHandled = timerHandled || did;
        }
        ...
    }
    

    __CFRunLoopDoTimer的主要逻辑是timer执行完毕后,会主动调用__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__函数👇

    // mode and rl are locked on entry and exit
    static Boolean __CFRunLoopDoTimer(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFRunLoopTimerRef rlt) { 
        ...
        __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(rlt->_callout, rlt, context_info);
        ...
    }
    

    正好与timer堆栈调用中的一致👇

    timer流程总结:

    1. 为自定义的timer,设置Mode,并将其加入RunLoop
    2. 在RunLoop的run方法执行时,会调用__CFRunLoopDoTimers执行所有timer
    3. 在__CFRunLoopDoTimers方法中,会通过for循环执行单个timer的操作
    4. 在__CFRunLoopDoTimer方法中,timer执行完毕后,会执行对应的timer回调

    以上,是针对timer的执行分析,对于observer、block、source0、source1,其执行原理与timer是类似的,这里就不再重复说明以下是苹果官方文档针对RunLoop处理不同源的图示👇

    2.4 RunLoop底层原理(伪代码)

    之前看RunLoop调用栈信息,我们知道RunLoop底层是通过CFRunLoopRun👇

    void CFRunLoopRun(void) {    /* DOES CALLOUT */
        int32_t result;
        do {
            // 1.0e10 : 科学技术 1*10^10
            result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
            CHECK_FOR_FORK();
        } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
    }
    

    果然,是个do-while循环,其中传入的参数1.0e10(科学计数) 等于 1* e^10,用于表示超时时间。接着看看CFRunLoopRunSpecific👇

    SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
        CHECK_FOR_FORK();
        if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
        __CFRunLoopLock(rl);
        
        //首先根据modeName找到对应mode
        CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
        
        // 通知 Observers: RunLoop 即将进入 loop。
        __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
        
        // 内部函数,进入loop
        result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
        
        // 通知 Observers: RunLoop 即将退出。
        __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
        
        return result;
        
    }
    

    大致流程如下👇
    首先根据modeName找到对应的mode类型,然后主要分为三种情况:

    1. 如果是entry,则通知observer,即将进入runloop
    2. 如果是exit,则通过observer,即将退出runloop
    3. 如果是其他中间状态,主要是通过runloop处理各种源

    接着看看核心流程__CFRunLoopRun,由于这部分代码较多,于是这里用伪代码代替。其主要逻辑是根据不同的事件源进行不同的处理,当RunLoop休眠时,可以通过相应的事件唤醒RunLoop👇

    //核心函数
    /* rl, rlm are locked on entrance and exit */
    static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode){
        
        //通过GCD开启一个定时器,然后开始跑圈
        dispatch_source_t timeout_timer = NULL;
        ...
        dispatch_resume(timeout_timer);
        
        int32_t retVal = 0;
        
        //处理事务,即处理items
        do {
            
            // 通知 Observers: 即将处理timer事件
            __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
            
            // 通知 Observers: 即将处理Source事件
            __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources)
            
            // 处理Blocks
            __CFRunLoopDoBlocks(rl, rlm);
            
            // 处理sources0
            Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
            
            // 处理sources0返回为YES
            if (sourceHandledThisLoop) {
                // 处理Blocks
                __CFRunLoopDoBlocks(rl, rlm);
            }
            
            // 判断有无端口消息(Source1)
            if (__CFRunLoopWaitForMultipleObjects(NULL, &dispatchPort, 0, 0, &livePort, NULL)) {
                // 处理消息
                goto handle_msg;
            }
            
            
            // 通知 Observers: 即将进入休眠
            __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
            __CFRunLoopSetSleeping(rl);
            
            // 等待被唤醒
            __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY);
            
            // user callouts now OK again
            __CFRunLoopUnsetSleeping(rl);
            
            // 通知 Observers: 被唤醒,结束休眠
            __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
            
            
        handle_msg:
            if (被timer唤醒) {
                // 处理Timers
                __CFRunLoopDoTimers(rl, rlm, mach_absolute_time());
            }else if (被GCD唤醒){
                // 处理gcd
                __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
            }else if (被source1唤醒){
                // 被Source1唤醒,处理Source1
                __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply)
            }
            
            // 处理block
            __CFRunLoopDoBlocks(rl, rlm);
            
            if (sourceHandledThisLoop && stopAfterHandle) {
                retVal = kCFRunLoopRunHandledSource;//处理源
            } else if (timeout_context->termTSR < mach_absolute_time()) {
                retVal = kCFRunLoopRunTimedOut;//超时
            } else if (__CFRunLoopIsStopped(rl)) {
                __CFRunLoopUnsetStopped(rl);
                retVal = kCFRunLoopRunStopped;//停止
            } else if (rlm->_stopped) {
                rlm->_stopped = false;
                retVal = kCFRunLoopRunStopped;//停止
            } else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
                retVal = kCFRunLoopRunFinished;//结束
            }
            
            
            
        }while (0 == retVal);
        
        return retVal;
    }
    

    至此,RunLoop的执行流程分析完毕,可以看看下面的经典图片👇

    总结

    本篇文章主要分析了两大知识点:AutoreleasePoolRunLoop,根据cpp和汇编查找入口,然后找到底层源码分析它们的实现流程,按照这个思路,我们终于清楚了系统是如何处理自动释放,RunLoop是如何处理各种事件源信息。相信大家在面试时碰到这些问题,就胸有成竹,应对自如了,哈哈!

    参考:
    iOS-底层原理 33:内存管理(三)AutoReleasePool & NSRunLoop 底层分析

    相关文章

      网友评论

        本文标题:自动释放池 & Runloop

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