iOS阅读理解-Runtime

iOS开发必须也是不得不了解的Runtime,今天有空能仔细的查阅下源码同时结合许多优秀的文章,对自己所掌握的runtime知识查漏补缺,文章只是代表自己目前的肤浅看法;
NSObject本质

#import @interface FooOC : NSObject @property (nonatomic, assign) int foo; @end @implementation FooOC @end int main(int argc, char * argv[]) { FooOC *foo = [[FooOC alloc] init]; return 0; }

通过clang -rewrite-objc main.m -o main.cpp指令把main.m转换成c++文件,可以找到如下代码
struct NSObject_IMPL { Class isa; }; struct FooOC_IMPL { struct NSObject_IMPL NSObject_IVARS; int _foo; };

可以看到FooOC中的foo变量在FooOC_IMPL转变为_foo,这就是为什么我们可以用foo->_foo访问变量的原因,此外NSObject还持有个isa变量;
NSLog(@"%zu", class_getInstanceSize([NSObject class])); //8 NSLog(@"%zu", class_getInstanceSize([FooOC class])); //16

通过class_getInstanceSize获取到NSObject实例大小为8个字节,所以意味着isa大小占8字节,而FooOC的int型变量占4个字节,但是因为需要内存补齐所以总大小为16字节。
//在objc.h中关于Class的定义 typedef struct objc_class *Class;

objc_object 在这里能下载到runtime源码;找到objc-runtime-new.h文件内关于objc_class的实现
struct objc_class : objc_object { // Class ISA; Class superclass; cache_t cache; class_data_bits_t bits; ...//剩下就是函数 }

struct objc_object { private: isa_t isa; public: Class ISA(); Class getIsa(); ... } union isa_t { isa_t() { } isa_t(uintptr_t value) : bits(value) { } Class cls; //nonpointer为0是取的这个值 uintptr_t bits; //typedef unsigned longuintptr_t; //nonpointer为1是这个值 //只看arm64 struct { // 0代表普通的指针,存储着Class,Meta-Class对象的内存地址。 // 1代表优化后的使用位域存储更多的信息。 uintptr_t nonpointer: 1; // 是否有设置过关联对象,如果没有,释放时会更快 uintptr_t has_assoc: 1; // 是否有C++析构函数,如果没有,释放时会更快 uintptr_t has_cxx_dtor: 1; // 存储着Class、Meta-Class对象的内存地址信息 uintptr_t shiftcls: 33; // 用于在调试时分辨对象是否未完成初始化 uintptr_t magic: 6; // 是否有被弱引用指向过。 uintptr_t weakly_referenced : 1; // 对象是否正在释放 uintptr_t deallocating: 1; // 引用计数器是否过大无法存储在isa中 // 如果为1,那么引用计数会存储在一个叫SideTable的类的属性中 uintptr_t has_sidetable_rc: 1; // 里面存储的值是引用计数器减1 uintptr_t extra_rc: 19; }; }

union(共同体)内的成员变量共享一块内存,即起始内存地址是一样的。struct里面变量后面跟着的冒号数字表示所占字节数,如uintptr_t nonpointer : 1; 表示nonpointer占1位,结构体总大小为64位(8字节)。但是在这里struct只是做解释说明作用,实际值存储在bits中,通过掩码取对应位的数据。

iOS阅读理解-Runtime
文章图片

如图在两次输出之间给self添加关联属性,最后一位数字5转为二进制为101,第三位1表示nonpointer为1,第二位has_assoc为0,添加关联属性后最后一位数字7转为二进制为111,第二位has_assoc为1。
iOS阅读理解-Runtime
文章图片

NSObject的isa指向NSObject meta Class,NSObject meta Class的super class指向NSObject,它的isa指向自己;
isa指针在nonpointer下是把元类、父类的内存地址信息储存在shiftcls,所以说我们输出对象的时候可以发现他们的地址最后一位要么是0,要么是8,因为他们的地址其实是isa & ISA_MASK,又因为ISA_MASK最后3位都是0(0000000ffffffff8 arm架构下),所以导致他们的地址最后3位也永远是0,所以最后一位要么是0(0000 0000),要么是8(0000 0100);
struct class_ro_t { uint32_t flags; uint32_t instanceStart; uint32_t instanceSize; #ifdef __LP64__ uint32_t reserved; #endif const uint8_t * ivarLayout; //保存着定义strong类型的变量 const char * name; method_list_t * baseMethodList; protocol_list_t * baseProtocols; const ivar_list_t * ivars; const uint8_t * weakIvarLayout; //保存着定义weak类型的变量 property_list_t *baseProperties; method_list_t *baseMethods() const { return baseMethodList; } };

class_ro_t是保存的是class基本信息,这里面的数据在初始化后就不能修的的,所有在runtime后动态添加的方法、变量都是储存在class_rw_t中;
当我们调用[NSObject alloc]方法时,调用的函数栈如下
alloc |__objc_rootAlloc(Class cls) |____callAlloc(Class cls, bool checkNil, bool allocWithZone=false) |_______objc_object::initInstanceIsa(Class cls, bool hasCxxDtor) |__________objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) |________objc_rootAllocWithZone(Class cls, malloc_zone_t *zone) |___________class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, bool cxxConstruct = true, size_t *outAllocatedSize = nil)static ALWAYS_INLINE id callAlloc(Class cls, bool checkNil, bool allocWithZone=false) { //类或superclass具有默认的alloc/allocWithZone时不执行fastpath, 这是存储在元类中的。 if (fastpath(!cls->ISA()->hasCustomAWZ())) { //没有自定义的alloc/allocWithZone实现 if (fastpath(cls->canAllocFast())) { // 没有c++的析构函数或者raw isa bool dtor = cls->hasCxxDtor(); //内存大小为bits记录的size id obj = (id)calloc(1, cls->bits.fastInstanceSize()); if (slowpath(!obj)) return callBadAllocHandler(cls); obj->initInstanceIsa(cls, dtor); return obj; } else { //有c++的析构函数或者raw isa id obj = class_createInstance(cls, 0); if (slowpath(!obj)) return callBadAllocHandler(cls); return obj; } } // 这里走下面的方法 if (allocWithZone) return [cls allocWithZone:nil]; return [cls alloc]; }static __attribute__((always_inline)) id _class_createInstanceFromZone(Class cls, size_t extraBytes, void *zone, bool cxxConstruct = true, size_t *outAllocatedSize = nil) { bool hasCxxDtor = cls->hasCxxDtor(); bool fast = cls->canAllocNonpointer(); //这里是class_ro_t的instancesize 即是nsobject isa 大小 size_t size = cls->instanceSize(extraBytes); if (outAllocatedSize) *outAllocatedSize = size; id obj; if (!zone&&fast) { obj = (id)calloc(1, size); if (!obj) return nil; obj->initInstanceIsa(cls, hasCxxDtor); } else { //objc2下zone永远为nil if (zone) { obj = (id)malloc_zone_calloc ((malloc_zone_t *)zone, 1, size); } else { obj = (id)calloc(1, size); } if (!obj) return nil; //使用原始指针isa obj->initIsa(cls); } if (cxxConstruct && hasCxxCtor) { obj = _objc_constructOrFree(obj, cls); } return obj; }

class_data_bits_t 【iOS阅读理解-Runtime】
iOS阅读理解-Runtime
文章图片
class_data_bits_t示意图
class_data_bits_t的bits通过掩码可以得到 class_rw_t信息, class_rw_t保存着属性列表、方法列表、协议等信息,保存的是二维数组,而 class_ro_t也储存的是属性列表、方法列表、协议等,但是是个一位数组,同时还多了ivars列表、instanceSize等;
static Class realizeClass(Class cls) { const class_ro_t *ro; class_rw_t *rw; Class supercls; Class metacls; bool isMeta; if (!cls) return nil; if (cls->isRealized()) return cls; ro = (const class_ro_t *)cls->data(); //这里判断cls->data()是否已经初始化 if (ro->flags & RO_FUTURE) { // rw data is already allocated. rw = cls->data(); ro = cls->data()->ro; cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE); } else { //开辟内存空间 rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1); rw->ro = ro; rw->flags = RW_REALIZED|RW_REALIZING; cls->setData(rw); } //当父类和元类没被初始化,初始化他们 supercls = realizeClass(remapClass(cls->superclass)); metacls = realizeClass(remapClass(cls->ISA())); ...//设置相关flag //这里初始化rw的方法、属性等列表 methodizeClass(cls); return cls; }

realizeClass是初始化class时候调用的,主要是开辟内存空间初始化class_rw_t,并且加载class_ro_t的方法属性等列表到class_rw_t;由次可见ro保存的是原始数据而rw是ro的一份copy,但是可以在此基础上添加;在methodizeClass会调用类似于cls->data()->methods.attachLists拷贝ro的方法到rw上面;
class_addMethod(Class cls, SEL name, IMP imp, const char *types) |_ addMethod(Class cls, SEL name, IMP imp, const char *types, bool replace) |____ cls->data()->methods.attachLists(&newlist, 1);

上面是class_addMethod函数调用栈,发现使用runtime动态添加方法时最终操作的是rw内存;因为编译时期确定好的是ro大小,ro保存了ivars_list,所以这就是为什么不能向编译后的类添加实例变量,因为ro里面的instance_size是确定的。
cache_t iOS阅读理解-Runtime
文章图片
//cache_t 初始化函数 void cache_t::reallocate(mask_t oldCapacity, mask_t newCapacity) { bool freeOld = canBeFreed(); bucket_t *oldBuckets = buckets(); //开辟空间=sizeof(bucket_t) * newCapacity bucket_t *newBuckets = allocateBuckets(newCapacity); //设置新bucket setBucketsAndMask(newBuckets, newCapacity - 1); //释放旧的cache if (freeOld) { cache_collect_free(oldBuckets, oldCapacity); cache_collect(false); } }

根据cache_t的初始化方法可以知道cache_t的_buckets大小为sizeof(bucket_t) * newCapacity,所以_buckets是一个bucket_t列表。
关于缓存释放该文件里面有个解释:
为了提高速度,objc_msgSend在读取方法缓存时不获取任何锁。反而将执行所有缓存更改,以便任何与缓存mutator并发运行的objc_msgSend都不会崩溃、挂起或从缓存中获得不正确的结果。当缓存内存未使用时(例如,缓存扩展后的旧缓存),它不会立即释放,因为并发objc_msgSend可能仍在使用它。当内存与数据结构断开连接会把它放在一个垃圾列表中。
//添加缓存 static void cache_fill_nolock(Class cls, SEL sel, IMP imp, id receiver) { //查找到缓存 直接return if (cache_getImp(cls, sel)) return; cache_t *cache = getCache(cls); cache_key_t key = getKey(sel); // mask_t newOccupied = cache->occupied() + 1; mask_t capacity = cache->capacity(); //如果缓存不足3/4,则按原样使用缓存 if (cache->isConstantEmptyCache()) { // 空的缓存 重新创建buckets cache->reallocate(capacity, capacity ?: INIT_CACHE_SIZE); } else if (newOccupied <= capacity / 4 * 3) { //缓存小于3/4满。按原样使用它。 } else { //缓存满了需要扩展。 cache->expand(); } //扫描第一个未使用的空间并插入其中。 //保证有一个空的空间,因为最小的尺寸是4,我们调整了3/4 full的大小。 bucket_t *bucket = cache->find(key, receiver); if (bucket->key() == 0) cache->incrementOccupied(); //将imp和key填入bucket bucket->set(key, imp); }bucket_t * cache_t::find(cache_key_t k, id receiver) { bucket_t *b = buckets(); mask_t m = mask(); //hash = key & mask mask_t begin = cache_hash(k, m); mask_t i = begin; do { //找到合适的位置 if (b[i].key() == 0||b[i].key() == k) { return &b[I]; } } while ((i = cache_next(i, m)) != begin); Class cls = (Class)((uintptr_t)this - offsetof(objc_class, cache)); cache_t::bad_cache(receiver, (SEL)k, cls); }

cache_fill_nolock是现实添加缓存的函数,通过代码来看_buckets是个典型的哈希表结构,避免了每次到方法列表查找imp,典型的用空间换时间的优化。
iOS阅读理解-Runtime
文章图片
objc_class构成
typedef struct objc_object *id;

平时用的id类型实际上就持有关键的isa_t,所以说编译器才能知道id所指向的具体信息;
消息发送
比如id obj = [NSObject alloc]; 可以翻译成下面这个

iOS阅读理解-Runtime
文章图片

sel_registerName点进去可以发现runtime内部维护了一张哈希表NXMapTable,所有SEL都在这张哈希表内储存,关于他的介绍在这 上古时代 Objective-C 中哈希表的实现
objc-msg-arm64.s中有关于objc_msgSend的汇编代码,主要做了以下事情
objc_msgSend |_LNilOrTagged |___LReturnZero //如果self = nil |___CacheLookup //先去查找缓存 |_____CacheHit // 缓存命中 |_______return imp |_____CacheMiss //缓存丢失 |__________objc_msgLookup_uncached |__________MethodTableLookup |______________class_lookupMethodAndLoadCache3

__class_lookupMethodAndLoadCache3内部调用的是lookUpImpOrForward
IMP lookUpImpOrForward(Class cls, SEL sel, id inst, bool initialize, bool cache, bool resolver) { IMP imp = nil; bool triedResolver = NO; if (!cls->isRealized()) { realizeClass(cls); } if (initialize&&!cls->isInitialized()) { _class_initialize (_class_getNonMetaClass(cls, inst)); } retry: //找到imp imp = cache_getImp(cls, sel); if (imp) goto done; { //在本类方法列表中查找method Method meth = getMethodNoSuper_nolock(cls, sel); if (meth) { //如果找到重新加入缓存中 log_and_fill_cache(cls, meth->imp, sel, inst, cls); imp = meth->imp; goto done; } } { unsigned attempts = unreasonableClassCount(); for (Class curClass = cls->superclass; curClass != nil; curClass = curClass->superclass) { if (--attempts == 0) { _objc_fatal("Memory corruption in class list."); } // 父类cache中查找imp imp = cache_getImp(curClass, sel); if (imp) { if (imp != (IMP)_objc_msgForward_impcache) { log_and_fill_cache(cls, imp, sel, inst, curClass); goto done; } else { break; } } //父类方法列表查询method Method meth = getMethodNoSuper_nolock(curClass, sel); if (meth) { log_and_fill_cache(cls, meth->imp, sel, inst, curClass); imp = meth->imp; goto done; } } } if (resolver&&!triedResolver) { //重新找一遍;可能因为加锁的原因导致方法列表或缓存已经改变了 _class_resolveMethod(cls, sel, inst); runtimeLock.read(); triedResolver = YES; goto retry; } //找不到;return消息转发的imp imp = (IMP)_objc_msgForward_impcache; cache_fill(cls, sel, imp, inst); return imp; }

1.lookUpImpOrForward会对没初始化的class初始化;
2.当cache miss时会去本类的方法列表查找方法,找的到return imp;
3.本类方法列表查找失败会去父类cache和方法列表查找,如果找不到去父类的父类查找。。。找到return imp;
4.因为没有使用锁所以方法列表或者缓存可能会改变,所以会再去查找一遍;不用锁是为了提高查找效率;
5.return 消息转发的imp;

iOS阅读理解-Runtime
文章图片
消息转发流程
自动释放池

iOS阅读理解-Runtime
文章图片

iOS阅读理解-Runtime
文章图片

从图上可以知道 @autoreleasepool {}实际上调用的是 _void * objc_autoreleasePoolPush(void),而 objc_autoreleasePoolPush 内部就是这个 AutoreleasePoolPage::push();这些可以在 NSObject.mm找到;
class AutoreleasePoolPage { #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 = PAGE_MAX_SIZE; // 4096bytes 每页的大小 static size_t const COUNT = SIZE / sizeof(id); //512 magic_t const magic; id *next; //指向page中下一个入栈的位置 pthread_t const thread; //page所处的线程 AutoreleasePoolPage * const parent; //指向上一张page AutoreleasePoolPage *child; //指向下一张page uint32_t const depth; //当前page的深度 uint32_t hiwat; ... }

AutoreleasePoolPage每页的大小为4kb(4096bytes),除去一些基本属性占用的大小剩下的都是用来储存加入自动释放池的obj,而parent、child很明显表示page和page之间的关系是一个双向链表;

iOS阅读理解-Runtime
文章图片

POOL_BOUNDARY为nil,起到哨兵作用,标明栈可以pop到哪里;
EMPTY_POOL_PLACEHOLDER储存在tls(Thread Local Storage-线程局部存);
在多线程的环境下,进程内的所有线程共享进程的数据空间。因此全局变量为所有线程共享。在程序设计中有时需要保存线程自己的全局变量,这种特殊的变量仅在线程内部有效。pthread_key是线程私有变量,它可以使多个线程用一个全局变量,但各自持有不同的值。 tls的创建是在runtime初始化中完成的,就是创建一个pthread_key;所以说每个线程的EMPTY_POOL_PLACEHOLDER都是独立的。
如图所示next指向的是下一块可用的空间;begin指向的是哨兵所在的位置,如果要快速释放所有obj可以找到哨兵并release之前的元素;
static inline void *push(){ id *dest; if (DebugPoolAllocation) { //每个自动释放池从一个新的页面开始。 dest = autoreleaseNewPage(POOL_BOUNDARY); } else { dest = autoreleaseFast(POOL_BOUNDARY); } assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY); return dest; }static __attribute__((noinline)) id *autoreleaseNewPage(id obj){ AutoreleasePoolPage *page = hotPage(); if (page) return autoreleaseFullPage(obj, page); else return autoreleaseNoPage(obj); }//hotPage表示正在使用的page 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; }static __attribute__((noinline)) id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page) { //当前页面已经满了。 //进入下一个新页面,必要时添加一个新页面,然后将对象添加到该页面。 assert(page == hotPage()); assert(page->full()||DebugPoolAllocation); do { //查找下一张page if (page->child) page = page->child; else page = new AutoreleasePoolPage(page); } while (page->full()); //置为当前page setHotPage(page); return page->add(obj); }//在这里 static __attribute__((noinline)) id *autoreleaseNoPage(id obj) { //no page可以表示没有释放池,或者已经池中只有一个空的占位符池,但还没有内容 assert(!hotPage()); bool pushExtraBoundary = false; if (haveEmptyPoolPlaceholder()) { //我们将第二个池推到空占位池上,或者将第一个对象推到空占位池中。 //在此之前,为当前由空占位池表示的池推一个池边界。 pushExtraBoundary = true; } else if (obj != POOL_BOUNDARY&&DebugMissingPools) { //obj不是哨兵(nil) objc_autoreleaseNoPool(obj); return nil; } else if (obj == POOL_BOUNDARY&&!DebugPoolAllocation) { //obj是哨兵,哨兵表示当前池内没有任何对象所以返回空池 return setEmptyPoolPlaceholder(); } //创建第一个自动释放池 AutoreleasePoolPage *page = new AutoreleasePoolPage(nil); setHotPage(page); //添加哨兵 if (pushExtraBoundary) { page->add(POOL_BOUNDARY); } //obj入栈page return page->add(obj); }id *add(id obj) { assert(!full()); //必须有空间 unprotect(); id *ret = next; *next++ = obj; protect(); return ret; }

1.当第一次push的时候会在autoreleaseNoPage这里创建第一张page添加哨兵后将添加自动释放池的对象压入栈。
static inline id *autoreleaseFast(id obj) { AutoreleasePoolPage *page = hotPage(); if (page && !page->full()) {//检查next指针是不是在栈定 return page->add(obj); } else if (page) { return autoreleaseFullPage(obj, page); } else { return autoreleaseNoPage(obj); } }

2.当第二次以后push会走到进入autoreleaseFast,如果当这个page满了(通过next指针判断是不是指向栈顶);
static inline void pop(void *token) { AutoreleasePoolPage *page; id *stop; if (token == (void*)EMPTY_POOL_PLACEHOLDER) { //弹出占位池 if (hotPage()) { //coldPage是pages链表中第一张page //coldPage->begin表示第一张page的第一个obj pop(coldPage()->begin()); } else { //pool重未使用过 清楚占位池 setHotPage(nil); } return; }page = pageForPointer(token); stop = (id *)token; //释放资源 page->releaseUntil(stop); if (DebugPoolAllocation&&page->empty()) { AutoreleasePoolPage *parent = page->parent; //释放所有page page->kill(); setHotPage(parent); } else if (DebugMissingPools&&page->empty()&&!page->parent) { page->kill(); setHotPage(nil); } else if (page->child) { //如果页面超过半满,则保留一个空的子页面 if (page->lessThanHalfFull()) { page->child->kill(); } else if (page->child->child) { page->child->child->kill(); } } }

3.pop操作接收一个token参数用来表示需要释放到哪里;
总结:autoreplease pool是一个双向链表,每个页面是一个栈,通过移动栈顶指针可以快速添加和释放池中的对象;
Super
clang转换可以看到super实质是objc_msgSendSuper;在message.h文件中找到关于他的定义objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )
struct objc_super { __unsafe_unretained _Nonnull id receiver; __unsafe_unretained _Nonnull Class super_class; }; - (instancetype)init { return [super init]; }

比如上面的init方法用clang转换出来可以得到下面这个
(objc_msgSendSuper)({(id)self, (id)class_getSuperclass(objc_getClass("NSObject"))}, sel_registerName("init"));
receiver是self,super_class是NSObject的isa指针即使NSObject;所以说当使用super的时候方法接受者是自己本身但是去查找init方法时是从super_class的方法列表去查找;
@interface FooOC : NSObject @property (nonatomic, assign) int foo; - (void)fooo; @end @implementation FooOC - (instancetype)init { return [super init]; } - (void)fooo { NSLog(@"%p,%p", [self class], [super class]); //print 0x101cec5b0,0x101cec5b0 } @end

fooo方法中的[super class]其实是[self class]只不过class方法是在foo的父类即NSObject中查找;
+ (Class)class { return self; }

但是根据class方法的实现最终返回的还是self即使foo本身所以两个输出是一样的。
Category实现
struct category_t { const char *name; classref_t cls; struct method_list_t *instanceMethods; struct method_list_t *classMethods; struct protocol_list_t *protocols; struct property_list_t *instanceProperties; struct property_list_t *_classProperties; method_list_t *methodsForMeta(bool isMeta) { if (isMeta) return classMethods; else return instanceMethods; } property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi); };

可以看到category_t结构体内并没有ivar_t,这就是category不支持定义变量的原因。通过xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc xx+xx.m的命令转换成cpp文件,可以在文件末尾找到关于category的实现;
//这部分是category实现 // @interface persion (foo) // @property (nonatomic, strong) NSSet *set; // @property (nonatomic, strong) NSString *str; // - (void)test; /* @end */ // @implementation persion (foo) // @dynamic set; //- (void)test {} //- (void)setStr:(NSString *)str {} //- (NSString *)str {return @""; } static void _I_persion_foo_test(persion * self, SEL _cmd) {} static void _I_persion_foo_setStr_(persion * self, SEL _cmd, NSString *str) {} static NSString * _I_persion_foo_str(persion * self, SEL _cmd) { return (NSString *)&__NSConstantStringImpl__var_folders_dp_m5qgk97s58s3_w2kz5z2trkw0000gn_T_persion_foo_d5c38b_mi_0; } // @end //添加的方法列表 static struct /*_method_list_t*/ { unsigned int entsize; // sizeof(struct _objc_method) unsigned int method_count; struct _objc_method method_list[3]; } _OBJC_$_CATEGORY_INSTANCE_METHODS_persion_$_foo __attribute__ ((used, section ("__DATA,__objc_const"))) = { sizeof(_objc_method), 3, {{(struct objc_selector *)"test", "v16@0:8", (void *)_I_persion_foo_test}, {(struct objc_selector *)"setStr:", "v24@0:8@16", (void *)_I_persion_foo_setStr_}, {(struct objc_selector *)"str", "@16@0:8", (void *)_I_persion_foo_str}} }; //属性列表 static struct /*_prop_list_t*/ { unsigned int entsize; // sizeof(struct _prop_t) unsigned int count_of_properties; struct _prop_t prop_list[2]; } _OBJC_$_PROP_LIST_persion_$_foo __attribute__ ((used, section ("__DATA,__objc_const"))) = { sizeof(_prop_t), 2, {{"set","T@\"NSSet\",&,D,N"}, {"str","T@\"NSString\",&,N"}} }; //创建category static struct _category_t _OBJC_$_CATEGORY_persion_$_foo __attribute__ ((used, section ("__DATA,__objc_const"))) = { "persion", 0, // &OBJC_CLASS_$_persion, (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_persion_$_foo,//这是上面那个 0, 0, (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_persion_$_foo,//这是上面那个 }; static void OBJC_CATEGORY_SETUP_$_persion_$_foo(void ) { _OBJC_$_CATEGORY_persion_$_foo.cls = &OBJC_CLASS_$_persion; //category的cls指针指向主类 }

可以从代码里面得知,编译器生成的category结构体里面保存了方法和属性,并将category的cls指针指向主类;
dyld(the dynamic link editor)是苹果的动态链接器,是苹果操作系统一个重要组成部分,在系统内核做好程序准备工作之后,交由dyld负责余下的工作。关于dyld的介绍点击dyld详解

iOS阅读理解-Runtime
文章图片

在dyld的过程中调用的 _objc_init,这个是runtime初始化的方法;
_objc_init |__ _dyld_objc_notify_register(&map_images, load_images, unmap_image)

_objc_init的函数调用栈,这里面做了超多事情,下面是我看代码和注释得出来的;
  • map_images
    map_images主要是处理由dyld映射的镜像、注册所有的类、修复或因延迟发现丢失的superclass等以及调用+load方法。
    1 找到所有带有Objective-C元数据的镜像并统计加载类的总数;
    2 执行runtime初始化,这里面包括
    • 注册SEL到哈希表(load、initialize、alloc等等方法,在objc-sel.mm的sel_init可以找到);
    • autorelease pool的初始化
    • SideTable的初始化,关于SideTable下面会说到;
    3 _read_images,这里面又做了
    • 去读取Mach-O文件中Segment section注册的class,然后调用readClass获取编译器编写的类(即是像上面cpp文件的代码)重新分配内存;关于Mach-O介绍可点击介绍
    • 同样去Segment section获取SEL、Message、非lazy加载的类(静态实例或使用+load方法)然后加载到内存,这里会调用上面提到的realizeClass;在Segment section中获取的包括引用到的类名和所有类名,这两个可能是不一样的,所以这里能对比二者得出未使用但是却引用的类、方法等;
    • 加载category,放到最后加载保证查找category方法、属性和协议时在原本类的前面,这里并没有覆盖原本的同名方法;
  • load_images
    调用所有load方法,注意这个category中的load方法要在本类的load方法调用之后才会调用;
  • unmap_image
    清理加载镜像现场;
Weak实现
首先来看个结构体
static StripedMap& SideTables() { return *reinterpret_cast*>(SideTableBuf); }struct SideTable { spinlock_t slock; //记录引用技术的hash表 RefcountMap refcnts; weak_table_t weak_table; ... }; //DenseMap是在llvm中用的非常广泛的数据结构,它本身的实现是一个基于Quadratic probing(二次探查)的散列表 typedef objc::DenseMap,size_t,true> RefcountMap; struct weak_table_t { weak_entry_t *weak_entries; size_tnum_entries; uintptr_t mask; uintptr_t max_hash_displacement; };

SideTables是一个全局哈希表,初始化的位置上面已经提到了,里面保存的是一个长度为64位的SideTable数组;
SideTable持有一个自旋锁、一个保存了引用计数的哈希表RefcountMap以及weak_table_t;

iOS阅读理解-Runtime
文章图片

weak_table_t也储存了一个哈希表 weak_entries,同时保存了entries的个数;
typedef DisguisedPtr weak_referrer_t; struct weak_entry_t { DisguisedPtr referent; union { struct { weak_referrer_t *referrers; uintptr_tout_of_line_ness : 2; uintptr_tnum_refs : PTR_MINUS_2; uintptr_tmask; uintptr_tmax_hash_displacement; }; struct { // out_of_line_ness字段是inline_referrers[1]的低位 weak_referrer_tinline_referrers[WEAK_INLINE_COUNT]; }; }; bool out_of_line() { return (out_of_line_ness == REFERRERS_OUT_OF_LINE); }weak_entry_t& operator=(const weak_entry_t& other) { memcpy(this, &other, sizeof(other)); return *this; }weak_entry_t(objc_object *newReferent, objc_object **newReferrer) : referent(newReferent) { inline_referrers[0] = newReferrer; for (int i = 1; i < WEAK_INLINE_COUNT; i++) { inline_referrers[i] = nil; } } };

weak_entry_t是保存单个weak对象的结构,其中referent保存的是weak引用对象的地址,类型是DisguisedPtr,这个class是作用时包装起来(实际上就是取反),这样内存分析工具就不会看到weak table的对象;union可能有两种类型,一种是referrers,可以指向多个weak_referrer_t,一种是只有4个长度的不可变数组inline_referrers;这两个都是用来保存weak引用对象指针的地址,当长度小于4使用第二个反之则使用第一个;如图显示,每个weak_table_t能保存多个weak_entry_t
下面来看看weak对象添加进全局weak表的函数weak_register_no_lock和移除函数weak_unregister_no_lock
id weak_register_no_lock(weak_table_t *weak_table, id referent_id, id *referrer_id, bool crashIfDeallocating) { objc_object *referent = (objc_object *)referent_id; objc_object **referrer = (objc_object **)referrer_id; //taggedPointer不支持引用计数 if (!referent||referent->isTaggedPointer()) return referent_id; // 确保弱引用对象可用 bool deallocating; if (!referent->ISA()->hasCustomRR()) { deallocating = referent->rootIsDeallocating(); } else { //对象支持弱引用的方法 BOOL (*allowsWeakReference)(objc_object *, SEL) = (BOOL(*)(objc_object *, SEL)) object_getMethodImplementation((id)referent, SEL_allowsWeakReference); if ((IMP)allowsWeakReference == _objc_msgForward) {//返回的是消息转发imp直接return nil return nil; } deallocating = ! (*allowsWeakReference)(referent, SEL_allowsWeakReference); }// now remember it and where it is being stored weak_entry_t *entry; //查找弱引用对象是否在weak_table if ((entry = weak_entry_for_referent(weak_table, referent))) { //weak添加新的引用对象 append_referrer(entry, referrer); } else { //当指定entry不存在则创建新的entry weak_entry_t new_entry(referent, referrer); weak_grow_maybe(weak_table); weak_entry_insert(weak_table, &new_entry); } return referent_id; }void weak_unregister_no_lock(weak_table_t *weak_table, id referent_id, id *referrer_id) { objc_object *referent = (objc_object *)referent_id; objc_object **referrer = (objc_object **)referrer_id; weak_entry_t *entry; if (!referent) return; //这里和上面一样获取引用对象的entry if ((entry = weak_entry_for_referent(weak_table, referent))) { remove_referrer(entry, referrer); bool empty = true; if (entry->out_of_line()&&entry->num_refs != 0) { empty = false; } else { for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) { if (entry->inline_referrers[i]) { empty = false; break; } } } if (empty) { //如果entry是没有任何元素则删除entry同时判断全局weak表是否需要收缩 weak_entry_remove(weak_table, entry); } }. }

当我们使用__weak其实是调用id objc_initWeak(id *location, id newObj)函数;initWeak内部调用的是storeWeak;
template static id storeWeak(id *location, objc_object *newObj) { if (!haveNew) assert(newObj == nil); Class previouslyInitializedClass = nil; id oldObj; SideTable *oldTable; SideTable *newTable; //获取新旧值的锁。 //按锁定地址下单,防止锁定下单问题。 //如果下面的旧值发生变化,请重试。 retry: if (haveOld) { //haveOld表示该weak引用的指针之前指过一个对象 oldObj = *location; oldTable = &SideTables()[oldObj]; } else { oldTable = nil; } if (haveNew) { newTable = &SideTables()[newObj]; } else { newTable = nil; }SideTable::lockTwo(oldTable, newTable); if (haveOld&&*location != oldObj) { //如果是haveOld则location和oldObj的地址应该是一样的 不一样表示可能被其他线程所修改 重新对oldObj赋值 SideTable::unlockTwo(oldTable, newTable); goto retry; } //保证所有newObj的isa都被初始化 if (haveNew&&newObj) { Class cls = newObj->getIsa(); if (cls != previouslyInitializedClass&& !((objc_class *)cls)->isInitialized()) { SideTable::unlockTwo(oldTable, newTable); _class_initialize(_class_getNonMetaClass(cls, (id)newObj)); previouslyInitializedClass = cls; //初始化class之后再去获取newobj goto retry; } } //清理旧值 if (haveOld) { weak_unregister_no_lock(&oldTable->weak_table, oldObj, location); } // 添加新值 if (haveNew) { newObj = (objc_object *) weak_register_no_lock(&newTable->weak_table, (id)newObj, location, crashIfDeallocating); //若weak_register_no_lock返回nil表示insert到weak_table失败 if (newObj&&!newObj->isTaggedPointer()) { newObj->setWeaklyReferenced_nolock(); } *location = (id)newObj; // 将weak指针指向newObj } else { //没有新的值,存储没有更改。} SideTable::unlockTwo(oldTable, newTable); return (id)newObj; }

梳理一下当调用initWeak的流程:
  1. 根据被weak对象所指对象(newObj)的地址在SideTables中查找SideTable,SideTables是一个长为64位的一张哈希表;
  2. 根据newObj的地址在这个SideTable的weak_table的weak_entries中获取entry实例;
    • 找的到entry表示newObj之前已经被其他weak对象所引用过了,首先先判断entry保存的weak对象是否大于4个,如果没有大于4个则直接把weak对象指针的指针地址添加到entry内部的inline_referrers(按顺序添加);如果大于4个则需要对inline_referrers扩容成referrers(可变数组),然后储存weak对象指针的指针,具体是把weak对象指针的指针地址算出在数组中保存的位置然后放进去;
    • 找不到entry则需要创建一个entry并且以newObj的地址算出来在weak_entries的位置然后添加进去;
然后看看对象调用dealloc的时候是怎么清理weak对象的;
void weak_clear_no_lock(weak_table_t *weak_table, id referent_id) { objc_object *referent = (objc_object *)referent_id; //dealloc对象的地址 weak_entry_t *entry = weak_entry_for_referent(weak_table, referent); if (entry == nil) { //获取不到entry表示没有weak引用对象,但是前面通过isa指针的标志位`weakly_referenced`判断过有才会进入到这个方法,这种情况不应该发送 return; } weak_referrer_t *referrers; size_t count; //判断entry的referrers是否大于4个 if (entry->out_of_line()) { referrers = entry->referrers; count = TABLE_SIZE(entry); } else { referrers = entry->inline_referrers; count = WEAK_INLINE_COUNT; }for (size_t i = 0; i < count; ++i) { objc_object **referrer = referrers[i]; //之前保存的是指针的指针 if (referrer) { if (*referrer == referent) { *referrer = nil; } else if (*referrer) { //是不应该走到这里的 _objc_inform("__weak variable at %p holds %p instead of %p. " "This is probably incorrect use of " "objc_storeWeak() and objc_loadWeak(). " "Break on objc_weak_error to debug.\n", referrer, (void*)*referrer, (void*)referent); objc_weak_error(); } } } weak_entry_remove(weak_table, entry); }

根据dealloc对象地址找到对应的SideTable然后在weak_table,再在weak_table取出该对象的entry,遍历entry的referrers释放weak指针对象;
引用计数
使用retain函数其实是调用rootRetain函数,下面看看他的实现
ALWAYS_INLINE id objc_object::rootRetain(bool tryRetain, bool handleOverflow) { if (isTaggedPointer()) return (id)this; //tagged pointer不需要引用计数bool sideTableLocked = false; bool transcribeToSideTable = false; isa_t oldisa; isa_t newisa; do { transcribeToSideTable = false; //获取对象的isa oldisa = LoadExclusive(&isa.bits); newisa = oldisa; //判断是否支持nonpointer优化 if (slowpath(!newisa.nonpointer)) { ClearExclusive(&isa.bits); if (!tryRetain && sideTableLocked) sidetable_unlock(); //不支持nonpointer优化意味着isa不能保存引用计数 则需要通过Sidetable来保存; if (tryRetain) return sidetable_tryRetain() ? (id)this : nil; else return sidetable_retain(); }//不检查newisa.fast_rr; 我们已经调用了RR重写 // don't check newisa.fast_rr; we already called any RR overrides if (slowpath(tryRetain && newisa.deallocating)) { ClearExclusive(&isa.bits); if (!tryRetain && sideTableLocked) sidetable_unlock(); return nil; } uintptr_t carry; //改变isa的extra_rc newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry); // extra_rc++if (slowpath(carry)) { // 如果newisa.extra_rc溢出 if (!handleOverflow) { ClearExclusive(&isa.bits); return rootRetain_overflow(tryRetain); } //isa的extra_rc保留计数的一半,准备将另一半复制到sidetable中 if (!tryRetain && !sideTableLocked) sidetable_lock(); sideTableLocked = true; transcribeToSideTable = true; newisa.extra_rc = RC_HALF; //标记sidetable需要处理 newisa.has_sidetable_rc = true; } } while (slowpath(!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits))); if (slowpath(transcribeToSideTable)) { //将retain counts的另一半复制到Sidetable sidetable_addExtraRC_nolock(RC_HALF); } if (slowpath(!tryRetain && sideTableLocked)) sidetable_unlock(); return (id)this; }

iOS阅读理解-Runtime
文章图片
ALWAYS_INLINE bool objc_object::rootRelease(bool performDealloc, bool handleUnderflow) { if (isTaggedPointer()) return false; bool sideTableLocked = false; isa_t oldisa; isa_t newisa; retry: do { //获取isa oldisa = LoadExclusive(&isa.bits); newisa = oldisa; if (slowpath(!newisa.nonpointer)) { //不使用nonpointer ClearExclusive(&isa.bits); if (sideTableLocked) sidetable_unlock(); return sidetable_release(performDealloc); } uintptr_t carry; newisa.bits = subc(newisa.bits, RC_ONE, 0, &carry); // extra_rc-- if (slowpath(carry)) { goto underflow; } } while (slowpath(!StoreReleaseExclusive(&isa.bits, oldisa.bits, newisa.bits))); if (slowpath(sideTableLocked)) sidetable_unlock(); return false; underflow: // newisa.extra_rc-- underflowed: //isa.extra_rc下溢 恢复newisa newisa = oldisa; if (slowpath(newisa.has_sidetable_rc)) { if (!handleUnderflow) { ClearExclusive(&isa.bits); return rootRelease_underflow(performDealloc); } // 将sidetable储存的引用计数放置到isa.extra_rc if (!sideTableLocked) { ClearExclusive(&isa.bits); sidetable_lock(); sideTableLocked = true; //重新开始,以避免与nonpotiner->raw pointer转换竞争 goto retry; } // 将sidetable的引用计数删除 size_t borrowed = sidetable_subExtraRC_nolock(RC_HALF); //为了避免竞争,has_sidetable_rc必须保持设置,即使sidetable的引用计数现在为零 if (borrowed > 0) { //sidetable引用计数减少。 //尝试将它们添加到extrac_rc中。 newisa.extra_rc = borrowed - 1; // redo the original decrement too bool stored = StoreReleaseExclusive(&isa.bits, oldisa.bits, newisa.bits); if (!stored) { //添加失败 isa_t oldisa2 = LoadExclusive(&isa.bits); isa_t newisa2 = oldisa2; if (newisa2.nonpointer) { //重新写入extrac_rc uintptr_t overflow; newisa2.bits = addc(newisa2.bits, RC_ONE * (borrowed-1), 0, &overflow); if (!overflow) { stored = StoreReleaseExclusive(&isa.bits, oldisa2.bits, newisa2.bits); } } } if (!stored) { //添加失败 //重新写回sidetable sidetable_addExtraRC_nolock(borrowed); goto retry; } //sidetable remove成功 sidetable_unlock(); return false; } else { // sidetable 是空的 } } if (slowpath(newisa.deallocating)) { ClearExclusive(&isa.bits); if (sideTableLocked) sidetable_unlock(); return overrelease_error(); } newisa.deallocating = true; if (!StoreExclusive(&isa.bits, oldisa.bits, newisa.bits)) goto retry; if (slowpath(sideTableLocked)) sidetable_unlock(); __sync_synchronize(); if (performDealloc) { //唤起dealloc ((void(*)(objc_object *, SEL))objc_msgSend)(this, SEL_dealloc); } return true; }

release的时候有点不一样,主要是区分是操作isa.extrac_rc还是操作SideTable,同时还有个发生下溢之后的复原工作,如果对象的引用计数为0时还会唤起对象的dealloc;
关联对象
iOS阅读理解-Runtime
文章图片

先来看看各种结构的含义:
  • ObjectAssociation
    对关联对象值和policy的封装;
class ObjcAssociation { uintptr_t _policy; //就是下面的枚举 id _value; //储存的值 public: ObjcAssociation(uintptr_t policy, id value) : _policy(policy), _value(value) {} ObjcAssociation() : _policy(0), _value(nil) {} uintptr_t policy() const { return _policy; } id value() const { return _value; } bool hasValue() { return _value != nil; } }; typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) { OBJC_ASSOCIATION_ASSIGN = 0, OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, OBJC_ASSOCIATION_COPY_NONATOMIC = 3, OBJC_ASSOCIATION_RETAIN = 01401, OBJC_ASSOCIATION_COPY = 01403 };

  • AssociationsMap
class ObjectAssociationMap : public std::map { public: void *operator new(size_t n) { return ::malloc(n); } void operator delete(void *ptr) { ::free(ptr); } };

map是STL的一个关联容器,它提供一对一(每个key只能在map中出现一次)的数据处理能力,map内部实现结构是红黑树,具有对数据自动排序的功能,所以在map内部所有的数据都是有序的;
  • AssociationsHashMap
class AssociationsHashMap : public unordered_map { public: void *operator new(size_t n) { return ::malloc(n); } void operator delete(void *ptr) { ::free(ptr); } };

unordered_map内部实现了一个哈希表,因此其元素的排列顺序是杂乱的,无序的;
  • AssociationsManager
    管理一个单例AssociationsHashMap;
objc_setAssociatedObject的函数栈
objc_setAssociatedObject |___object_set_associative_reference(id object, void *key, id value, uintptr_t policy)

void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) { ObjcAssociation old_association(0, nil); id new_value = https://www.it610.com/article/value ? acquireValue(value, policy) : nil; { AssociationsManager manager; AssociationsHashMap &associations(manager.associations()); //获取单例哈希表AssociationsHashMap //对object(object如果是self就是self的isa地址)取反 disguised_ptr_t disguised_object = DISGUISE(object); if (new_value) { AssociationsHashMap::iterator i = associations.find(disguised_object); if (i != associations.end()) { //表示object是被其他属性关联过的 ObjectAssociationMap *refs = i->second; ObjectAssociationMap::iterator j = refs->find(key); if (j != refs->end()) { old_association = j->second; j->second = ObjcAssociation(policy, new_value); } else { (*refs)[key] = ObjcAssociation(policy, new_value); } } else { // create the new association (first time). //创建新的关联(第一次) ObjectAssociationMap *refs = new ObjectAssociationMap; associations[disguised_object] = refs; (*refs)[key] = ObjcAssociation(policy, new_value); object->setHasAssociatedObjects(); } } else { //将关联设置为nil会破坏关联。 AssociationsHashMap::iterator i = associations.find(disguised_object); if (i !=associations.end()) { ObjectAssociationMap *refs = i->second; ObjectAssociationMap::iterator j = refs->find(key); if (j != refs->end()) { old_association = j->second; refs->erase(j); } } } } if (old_association.hasValue()) ReleaseValue()(old_association); }

同时在对象dealloc的时候也会调用_object_remove_assocations清除关联对象;具体的做法就是查找该对象的ObjectAssociationMap然后释放掉所有的value;
总结
runtime涉及的知识点太多了,源码只看一遍肯定是不够的,这里只是对runtime有个初步的认识和了解,每个知识点都可以单独拉出来写一篇文章,写文章的好处就在于字打出来印象会比较深刻同时还有笔记记录以后复习的时候比较方便捡起来。
图片来源以及参考,感谢大佬们的分享
自动释放池的前世今生
iOS底层原理总结 - 探寻Runtime本质
iOS 程序 main 函数之前发生了什么
Objective-C runtime机制(7)——SideTables, SideTable, weak_table, weak_entry_t
神经病院Objective-C Runtime入院第一天——isa和Class
# 深入理解Tagged Pointer

    推荐阅读