iOS开发之runtime(21):arr_init()分析

logo

本系列博客是本人的源码阅读笔记,如果有 iOS 开发者在看 runtime 的,欢迎大家多多交流。为了方便讨论,本人新建了一个微信群(iOS技术讨论群),想要加入的,请添加本人微信:zhujinhui207407,【加我前请备注:ios 】,本人博客http://www.kyson.cn 也在不停的更新中,欢迎一起讨论

本文完整版详见笔者小专栏:https://xiaozhuanlan.com/runtime

引言

笔者在之前的文章iOS开发之runtime(17):_dyld_objc_notify_register方法介绍中大概提过,arr_init的作用是自动释放迟的初始化,那arr_init究竟做了哪些事情,有哪些我们需要注意的地方,本文就带大家一起分析该方法。

分析

该方法源代码如下

void arr_init(void) 
{
    AutoreleasePoolPage::init();
    SideTableInit();
}

里面看到了两个名词

  • AutoreleasePoolPage
  • SideTable

这里对他们做个大概介绍:

AutoreleasePoolPage

做过iOS开发的同事都知道autoreleasepool的作用,这里的init方法看来就是AutoreleasePoolPage初始化的位置了,那autoreleasepoolautoreleasepool有什么区别与联系呢。我们首先看一下AutoreleasePoolPage的类构成:

class AutoreleasePoolPage {
    // EMPTY_POOL_PLACEHOLDER is stored in TLS when exactly one pool is 
    // pushed and it has never contained any objects. This saves memory 
    // when the top level (i.e. libdispatch) pushes and pops pools but 
    // never uses them.
#   define EMPTY_POOL_PLACEHOLDER ((id*)1)

#   define POOL_BOUNDARY nil
    static pthread_key_t const key = AUTORELEASE_POOL_KEY;
    static uint8_t const SCRIBBLE = 0xA3;  // 0xA3A3A3A3 after releasing
    static size_t const SIZE = 
#if PROTECT_AUTORELEASEPOOL
        PAGE_MAX_SIZE;  // must be multiple of vm page size
#else
        PAGE_MAX_SIZE;  // size and alignment, power of 2
#endif
    static size_t const COUNT = SIZE / sizeof(id);

    magic_t const magic;
    id *next;
    pthread_t const thread;
    AutoreleasePoolPage * const parent;
    AutoreleasePoolPage *child;
    uint32_t const depth;
    uint32_t hiwat;
};
  • 由于
// Set this to 1 to mprotect() autorelease pool contents
#define PROTECT_AUTORELEASEPOOL 0

因此PROTECT_AUTORELEASEPOOL中的代码不会执行

  • PAGE_MAX_SIZE的值经过分析,发现是4096
  • AUTORELEASE_POOL_KEY的值是43
class AutoreleasePoolPage {
    // EMPTY_POOL_PLACEHOLDER is stored in TLS when exactly one pool is 
    // pushed and it has never contained any objects. This saves memory 
    // when the top level (i.e. libdispatch) pushes and pops pools but 
    // never uses them.
#   define EMPTY_POOL_PLACEHOLDER ((id*)1)

#   define POOL_BOUNDARY nil
    static pthread_key_t const key = 43;
    static uint8_t const SCRIBBLE = 0xA3;  // 0xA3A3A3A3 after releasing
    static size_t const SIZE =  4096;  // size and alignment, power of 2
    static size_t const COUNT = SIZE / sizeof(id);

    magic_t const magic;
    id *next;
    pthread_t const thread;
    AutoreleasePoolPage * const parent;
    AutoreleasePoolPage *child;
    uint32_t const depth;
    uint32_t hiwat;
};

从类AutoreleasePoolPage暴露的几个属性也可以大概猜出,AutoreleasePoolPage是个链表结构,有parent以及child
链表可以通过栈实现,也可以通过队列实现。这里我们看到了push以及pop方法,那应该就是个栈的实现了
这里去掉部分冗余逻辑,代码整理如下:

static inline void *push() 
{
    //// 添加一个哨兵对象到自动释放池的链表栈中
    id *dest = autoreleaseFast(POOL_BOUNDARY);
    return dest;
}

static inline void pop(void *token) 
{
    AutoreleasePoolPage *page;
    id *stop;

    if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
        // Popping the top-level placeholder pool.
        if (hotPage()) {
            // Pool was used. Pop its contents normally.
            // Pool pages remain allocated for re-use as usual.
            pop(coldPage()->begin());
        } else {
            // Pool was never used. Clear the placeholder.
            setHotPage(nil);
        }
        return;
    }

    page = pageForPointer(token);
    stop = (id *)token;
    if (*stop != POOL_BOUNDARY) {
        if (stop == page->begin()  &&  !page->parent) {
            // Start of coldest page may correctly not be POOL_BOUNDARY:
            // 1. top-level pool is popped, leaving the cold page in place
            // 2. an object is autoreleased with no pool
        } else {
            // Error. For bincompat purposes this is not 
            // fatal in executables built with old SDKs.
            return badPop(token);
        }
    }

    if (PrintPoolHiwat) printHiwat();

    page->releaseUntil(stop);

    // memory: delete empty children
    if (DebugPoolAllocation  &&  page->empty()) {
        // special case: delete everything during page-per-pool debugging
        AutoreleasePoolPage *parent = page->parent;
        page->kill();
        setHotPage(parent);
    } else if (DebugMissingPools  &&  page->empty()  &&  !page->parent) {
        // special case: delete everything for pop(top) 
        // when debugging missing autorelease pools
        page->kill();
        setHotPage(nil);
    } 
    else if (page->child) {
        // hysteresis: keep one empty child if page is more than half full
        if (page->lessThanHalfFull()) {
            page->child->kill();
        }
        else if (page->child->child) {
            page->child->child->kill();
        }
    }
}

这里有个hotPage以及codePage的概念需要了解一下。这里先不做过多解释,笔者会在后面的文章中给出。

由于arr_init调用的是函数AutoreleasePoolPage::init();其实现如下:

static void init()
{
    int r __unused = pthread_key_init_np(AutoreleasePoolPage::key, 
                                         AutoreleasePoolPage::tls_dealloc);
    assert(r == 0);
}

看来了解函数pthread_key_init_np函数很有必要。该函数分为两部分pthread_key_init以及np。第一部分很好理解,就是pthread的键初始化;第二部分的np表示不可移植(np意指non portable,不可移植)。
AutoreleasePoolPage::key的值我们也在刚刚类定义的部分看到了:

//AUTORELEASE_POOL_KEY 就是等于43
    static pthread_key_t const key = AUTORELEASE_POOL_KEY;

那么pthread_key_init_np这个方法的具体含义是什么呢?我们看一下pthread的源码:https://github.com/apple/darwin-libpthread找到其实现

int
pthread_key_init_np(int key, void (*destructor)(void *))
{
    int res = EINVAL; // Returns EINVAL if key is out of range.
    if (key >= __pthread_tsd_first && key < __pthread_tsd_start) {
        _PTHREAD_LOCK(__pthread_tsd_lock);
        _pthread_key_set_destructor(key, destructor);
        if (key > __pthread_tsd_max) {
            __pthread_tsd_max = key;
        }
        _PTHREAD_UNLOCK(__pthread_tsd_lock);
        res = 0;
    }
    return res;
}

可以发现,key其实是个整数,且有一定的范围限制。笔者宏定义,看过之后发现,最小值为

#define __TSD_RESERVED_MAX 9

最大值为:

#define _INTERNAL_POSIX_THREAD_KEYS_MAX 256

因此我们的43其实是在正确值范围之内的。验证完后,会调用方法:

_pthread_key_set_destructor(key, destructor);

看来其实只是简单的设置一个析构函数而已。这个我们就不做深究了,后面如果我们碰到再继续研究。

SideTable

SideTableInit()方法也大概能猜测是对一个称为SideTable的对象进行初始化,其源代码如下:

static void SideTableInit() {
    new (SideTableBuf) StripedMap();
}

可以发现,其实是初始化了类StripedMap。那么StripedMap又是什么呢,我们看一下:

template
class StripedMap {

    enum { CacheLineSize = 64 };

#if TARGET_OS_EMBEDDED
    enum { StripeCount = 8 };
#else
    enum { StripeCount = 64 };
#endif

    struct PaddedT {
        T value alignas(CacheLineSize);
    };

    PaddedT array[StripeCount];

    static unsigned int indexForPointer(const void *p) {
        uintptr_t addr = reinterpret_cast(p);
        return ((addr >> 4) ^ (addr >> 9)) % StripeCount;
    }

 public:
    T& operator[] (const void *p) { 
        return array[indexForPointer(p)].value; 
    }
    const T& operator[] (const void *p) const { 
        return const_cast>(this)[p]; 
    }
};

SideTable的代码如下:

struct SideTable {
    spinlock_t slock;
    RefcountMap refcnts;
    weak_table_t weak_table;

    SideTable() {
        memset(&weak_table, 0, sizeof(weak_table));
    }

    ~SideTable() {
        _objc_fatal("Do not delete SideTable.");
    }

    void lock() { slock.lock(); }
    void unlock() { slock.unlock(); }

    // Address-ordered lock discipline for a pair of side tables.

    template
    static void lockTwo(SideTable *lock1, SideTable *lock2);
    template
    static void unlockTwo(SideTable *lock1, SideTable *lock2);
};

可以发现,这个类也是略微复杂。等笔者后面再继续分析好了。

总结

本文分析了方法arr_init()。大概了解了几个名词

  • AutoReleasePoolPage
  • SideTable
  • StripedMap
    大概知道AutoReleasePoolPage是个链表,SideTable是个比较复杂的结构体/类。后面笔者再细细分析吧。

广告

我的首款个人开发的APP壁纸宝贝上线了,欢迎大家下载。

壁纸宝贝

你可能感兴趣的:(iOS开发之runtime(21):arr_init()分析)