iOS-Autorelease Pool

自动释放池 & Runloop

2020-12-03  本文已影响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循环的区别👇

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:

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进行匹配处理:

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源码,针对不同的对象,有不同的处理

接着我们看看__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 底层分析

上一篇下一篇

猜你喜欢

热点阅读