本系列博客是本人的源码阅读笔记,如果有 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
初始化的位置了,那autoreleasepool
和autoreleasepool
有什么区别与联系呢。我们首先看一下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壁纸宝贝上线了,欢迎大家下载。