深刻淺出 Runtime(一):初識
深刻淺出 Runtime(二):數據結構
深刻淺出 Runtime(三):消息機制
深刻淺出 Runtime(四):super 的本質
深刻淺出 Runtime(五):相關面試題html
Objective-C
的面向對象都是基於C/C++
的數據結構——結構體實現的。
咱們平時使用的全部對象都是id
類型,id
類型對象對應到runtime
中,就是objc_object
結構體。面試
// A pointer to an instance of a class. typedef struct objc_object *id; 複製代碼
struct objc_object { private: isa_t isa; /*... isa操做相關 弱引用相關 關聯對象相關 內存管理相關 ... */ }; 複製代碼
Class
指針用來指向一個 Objective-C 的類,它是objc_class
結構體類型,因此class、meta-class
底層結構都是objc_class
結構體,objc_class
繼承自objc_object
,因此它也有isa
指針,它也是對象。數組
// An opaque type that represents an Objective-C class. typedef struct objc_class *Class; 複製代碼
struct objc_class : objc_object { // Class ISA; Class superclass; // 指向父類 cache_t cache; // 方法緩存 formerly cache pointer and vtable class_data_bits_t bits; // 用於獲取具體的類信息 class_rw_t * plus custom rr/alloc flags class_rw_t *data() { return bits.data(); } }; 複製代碼
class_data_bits_t
主要是對class_rw_t
的封裝,能夠經過bits & FAST_DATA_MASK
得到class_rw_t
。struct class_data_bits_t { // Values are the FAST_ flags above. uintptr_t bits; public: class_rw_t* data() { return (class_rw_t *)(bits & FAST_DATA_MASK); } }; 複製代碼
class_rw_t
表明了類相關的讀寫信息,它是對class_ro_t
的封裝;class_rw_t
中主要存儲着類的方法列表、屬性列表、協議列表等;class_rw_t
裏面的methods
、properties
、protocols
都繼承於list_array_tt
二維數組,是可讀可寫的,包含了類的初始內容、分類的內容。struct class_rw_t { // Be warned that Symbolication knows the layout of this structure. uint32_t flags; uint32_t version; const class_ro_t *ro; method_array_t methods; // 方法列表 property_array_t properties; // 屬性列表 protocol_array_t protocols; // 協議列表 Class firstSubclass; Class nextSiblingClass; char *demangledName; }; 複製代碼
class_ro_t
表明了類相關的只讀信息;class_ro_t
中主要存儲着類的成員變量列表、類名等;class_ro_t
裏面的baseMethodList
、baseProtocols
、ivars
、baseProperties
是一維數組,是隻讀的,包含了類的初始內容;class_ro_t
裏,當程序運行時,通過一系列的函數調用棧,在realizeClass()
函數中,將class_ro_t
裏的東西和分類的東西合併起來放到class_rw_t
裏,並讓bits
指向class_rw_t
。struct class_ro_t { uint32_t flags; uint32_t instanceStart; uint32_t instanceSize; // instance對象佔用的內存空間 #ifdef __LP64__ uint32_t reserved; #endif const uint8_t * ivarLayout; const char * name; // 類名 method_list_t * baseMethodList; protocol_list_t * baseProtocols; const ivar_list_t * ivars; // 成員變量列表 const uint8_t * weakIvarLayout; property_list_t *baseProperties; method_list_t *baseMethods() const { return baseMethodList; } }; 複製代碼
method_array_t
與method_list_t
。cache
中,下一次調用這些方法的命中率就會更高些);f(@selector()) = index, @selector() & _mask
;runtime
會將這個方法緩存到cache
中,下次再調用此方法的時候,runtime
會優先去cache
中查找。struct cache_t { struct bucket_t *_buckets; // 哈希表 mask_t _mask; // 哈希表的長度 - 1 mask_t _occupied; // 已經緩存的方法數量 }; struct bucket_t { private: cache_key_t _key; // SEL IMP _imp; // IMP 函數的內存地址 }; 複製代碼
//objc-cache.mm(objc4) bucket_t * cache_t::find(cache_key_t k, id receiver) // 根據 k 即 @selector 進行查找 { assert(k != 0); bucket_t *b = buckets(); // 獲取_buckets mask_t m = mask(); // 獲取_mask mask_t begin = cache_hash(k, m); // 計算起始索引 mask_t i = begin; do { // 根據索引 i 從 _buckets 哈希表中取值 // 若是取出來的 bucket_t 的 _key = 0,說明在索引的位置上尚未緩存過方法,返回該 bucket_t,停止緩存查詢,用於 cache_fill_nolock() 函數 // 若是取出來的 bucket_t 的 _key = k,說明查詢成功,返回該 bucket_t if (b[i].key() == 0 || b[i].key() == k) { return &b[i]; } // 在 __arm64__ 下將索引 i -1,繼續查找,反向遍歷 _buckets 哈希表 // 直到 i 指向首個元素即索引 = 0 時,將 mask 賦值給 i,使其指向哈希表最後一個元素,繼續反向遍歷 // 若是此時尚未找到 k 對應的 bucket_t ,或者是空的 bucket_t ,則循環結束,查找失敗,調用 bad_cache() 函數 // 接下來去類對象中 class_rw_t 中的 methods 查找 } while ((i = cache_next(i, m)) != begin); // hack Class cls = (Class)((uintptr_t)this - offsetof(objc_class, cache)); cache_t::bad_cache(receiver, (SEL)k, cls); } static inline mask_t cache_hash(cache_key_t key, mask_t mask) { return (mask_t)(key & mask); } static inline mask_t cache_next(mask_t i, mask_t mask) { // return (i+1) & mask; // __arm__ || __x86_64__ || __i386__ return i ? i-1 : mask; // __arm64__ } 複製代碼
//objc-cache.mm(objc4) static void cache_fill_nolock(Class cls, SEL sel, IMP imp, id receiver) { cacheUpdateLock.assertLocked(); // Never cache before +initialize is done if (!cls->isInitialized()) return; // 若是類還未初始化,直接返回 // Make sure the entry wasn't added to the cache by some other thread // before we grabbed the cacheUpdateLock. if (cache_getImp(cls, sel)) return; // 可能有其它線程搶先將該方法緩存了,因此要檢查一次緩存,若是存在,直接返回 cache_t *cache = getCache(cls); // ️取出該 class 的 cache_t cache_key_t key = getKey(sel); // ️根據 sel 得到 _key // Use the cache as-is if it is less than 3/4 full mask_t newOccupied = cache->occupied() + 1; // 將 cache_t 的 _occupied 即已經緩存的方法數量 + 1,這裏只是爲了判斷 +1 後緩存容量是否滿 mask_t capacity = cache->capacity(); // 得到緩存容量 = _mask + 1 if (cache->isConstantEmptyCache()) { // 若是緩存是隻讀的,從新申請緩存空間 // Cache is read-only. Replace it. cache->reallocate(capacity, capacity ?: INIT_CACHE_SIZE); // 申請新的緩存空間,並釋放舊的 } else if (newOccupied <= capacity / 4 * 3) { // ️若是當前已經緩存的方法數量 +1 <= 緩存容量的 3/4,就繼續往下操做 // Cache is less than 3/4 full. Use it as-is. } else { // ️若是以上條件不知足,說明緩存已滿,進行緩存擴容 // Cache is too full. Expand it. cache->expand(); } // Scan for the first unused slot and insert there. // 掃描第一個未使用的插槽(bucket_t)並將其插入 // There is guaranteed to be an empty slot because the // 必然會有一個空的插槽(bucket_t) // minimum size is 4 and we resized at 3/4 full. // 由於最小大小是4,咱們調整爲3/4滿 bucket_t *bucket = cache->find(key, receiver); // ️調用 find() 函數進行一次緩存查找,必然會獲得一個空的 bucket_t if (bucket->key() == 0) cache->incrementOccupied(); // ️若是該 bucket_t 爲空,將 _occupied 即已經緩存的方法數量 + 1 bucket->set(key, imp); // ️添加緩存 } void cache_fill(Class cls, SEL sel, IMP imp, id receiver) { #if !DEBUG_TASK_THREADS mutex_locker_t lock(cacheUpdateLock); cache_fill_nolock(cls, sel, imp, receiver); #else _collecting_in_critical(); return; #endif } 複製代碼
bucket_t
,容量 = 舊的兩倍;_mask
=bucket_t
長度 - 1;runtime
動態交換方法實現時也會釋放緩存)。//objc-cache.mm(objc4) void cache_t::expand() { cacheUpdateLock.assertLocked(); uint32_t oldCapacity = capacity(); // ️將緩存擴容爲原來的兩倍,若是是首次調用,設置緩存容量的初始值爲 4 uint32_t newCapacity = oldCapacity ? oldCapacity*2 : INIT_CACHE_SIZE; if ((uint32_t)(mask_t)newCapacity != newCapacity) { // mask overflow - can't grow further // fixme this wastes one bit of mask newCapacity = oldCapacity; } reallocate(oldCapacity, newCapacity); // ️申請新的緩存空間,並釋放舊的 } enum { INIT_CACHE_SIZE_LOG2 = 2, INIT_CACHE_SIZE = (1 << INIT_CACHE_SIZE_LOG2) }; void cache_t::reallocate(mask_t oldCapacity, mask_t newCapacity) { bool freeOld = canBeFreed(); // ️判斷一下緩存是否是空的,若是爲空,就不必釋放空間 bucket_t *oldBuckets = buckets(); bucket_t *newBuckets = allocateBuckets(newCapacity); // Cache's old contents are not propagated. // This is thought to save cache memory at the cost of extra cache fills. // fixme re-measure this assert(newCapacity > 0); assert((uintptr_t)(mask_t)(newCapacity-1) == newCapacity-1); setBucketsAndMask(newBuckets, newCapacity - 1); if (freeOld) { cache_collect_free(oldBuckets, oldCapacity); cache_collect(false); } } bool cache_t::canBeFreed() { return !isConstantEmptyCache(); } bool cache_t::isConstantEmptyCache() { return occupied() == 0 && buckets() == emptyBucketsForCapacity(capacity(), false); } 複製代碼
cache_t
的內容,請查看:isa
指針用來維護對象和類之間的關係,並確保對象和類可以經過isa
指針找到對應的方法、實例變量、屬性、協議等;isa
就是一個普通的指針,直接指向objc_class
,存儲着Class
、Meta-Class
對象的內存地址。instance
對象的isa
指向class
對象,class
對象的isa
指向meta-class
對象;isa
進行了優化,變成了一個共用體(union
)結構,還使用位域來存儲更多的信息。將 64 位的內存數據分開來存儲着不少的東西,其中的 33 位纔是拿來存儲class
、meta-class
對象的內存地址信息。要經過位運算將isa
的值& ISA_MASK
掩碼,才能獲得class
、meta-class
對象的內存地址。struct objc_object { Class isa; // 在 arm64 架構以前 }; struct objc_object { private: isa_t isa; // 在 arm64 架構開始 }; union isa_t { isa_t() { } isa_t(uintptr_t value) : bits(value) { } Class cls; uintptr_t bits; #if SUPPORT_PACKED_ISA // extra_rc must be the MSB-most field (so it matches carry/overflow flags) // nonpointer must be the LSB (fixme or get rid of it) // shiftcls must occupy the same bits that a real class pointer would // bits + RC_ONE is equivalent to extra_rc + 1 // RC_HALF is the high bit of extra_rc (i.e. half of its range) // future expansion: // uintptr_t fast_rr : 1; // no r/r overrides // uintptr_t lock : 2; // lock for atomic property, @synch // uintptr_t extraBytes : 1; // allocated with extra bytes # if __arm64__ # define ISA_MASK 0x0000000ffffffff8ULL // 用來取出 Class、Meta-Class 對象的內存地址 # define ISA_MAGIC_MASK 0x000003f000000001ULL # define ISA_MAGIC_VALUE 0x000001a000000001ULL struct { uintptr_t nonpointer : 1; // 0:表明普通的指針,存儲着 Class、Meta-Class 對象的內存地址 // 1:表明優化過,使用位域存儲更多的信息 uintptr_t has_assoc : 1; // 是否有設置過關聯對象,若是沒有,釋放時會更快 uintptr_t has_cxx_dtor : 1; // 是否有C++的析構函數(.cxx_destruct),若是沒有,釋放時會更快 uintptr_t shiftcls : 33; // 存儲着 Class、Meta-Class 對象的內存地址信息 uintptr_t magic : 6; // 用於在調試時分辨對象是否未完成初始化 uintptr_t weakly_referenced : 1; // 是否有被弱引用指向過,若是沒有,釋放時會更快 uintptr_t deallocating : 1; // 對象是否正在釋放 uintptr_t has_sidetable_rc : 1; // 若是爲1,表明引用計數過大沒法存儲在 isa 中,那麼超出的引用計數會存儲在一個叫 SideTable 結構體的 RefCountMap(引用計數表)哈希表中 uintptr_t extra_rc : 19; // 裏面存儲的值是引用計數 retainCount - 1 # define RC_ONE (1ULL<<45) # define RC_HALF (1ULL<<18) }; }; 複製代碼
class
、meta-class
底層結構都是objc_class
結構體,objc_class
繼承自objc_object
,因此它也有isa
指針,因此它也是對象;class
中存儲着實例方法、成員變量、屬性、協議等信息, meta-class
中存儲着類方法等信息;isa
指針和superclass
指針的指向(如上圖);meta-class
的superclass
指向基類的class
, 決定了一個性質:當咱們調用一個類方法,會經過class
的isa
指針找到meta-class
,在meta-class
中查找有無該類方法,若是沒有,再經過meta-class
的superclass
指針逐級查找父meta-class
,一直找到基類的meta-class
若是還沒找到該類方法的話,就會去找基類的class
中同名的實例方法的實現。- (Class)class; + (Class)class; Class object_getClass(id obj); // 傳參:instance 對象 複製代碼
Class object_getClass(id obj); // 傳參:Class 對象 複製代碼
示例代碼以下緩存
NSObject *object1 = [NSObject alloc] init]; NSObject *object2 = [NSObject alloc] init]; // objectClass1 ~ objectClass5 都是 NSObject 的類對象 Class objectClass1 = [object1 class]; Class objectClass2 = [object2 class]; Class objectClass3 = [NSObject class]; Class objectClass4 = object_getClass(object1); Class objectClass5 = object_getClass(object2); // objectMetaClass1 ~ objectMetaClass4 都是 NSObject 的元類對象 Class objectMetaClass1 = object_getClass([object1 class]; Class objectMetaClass2 = object_getClass([NSObject class]); Class objectMetaClass3 = object_getClass(object_getClass(object1)); Class objectMetaClass4 = object_getClass(objectClass5); 複製代碼
方法實現bash
- (Class)class { return object_getClass(self); } + (Class)class { return self; } Class object_getClass(id obj) { if (obj) return obj->getIsa(); else return Nil; } objc_object::getIsa() { if (!isTaggedPointer()) return ISA(); ...... } objc_object::ISA() { assert(!isTaggedPointer()); #if SUPPORT_INDEXED_ISA if (isa.nonpointer) { uintptr_t slot = isa.indexcls; return classForIndex((unsigned)slot); } return (Class)isa.bits; #else return (Class)(isa.bits & ISA_MASK); #endif } #if __ARM_ARCH_7K__ >= 2 # define SUPPORT_INDEXED_ISA 1 #else # define SUPPORT_INDEXED_ISA 0 #endif 複製代碼
目的是將實例和類的相關方法列表以及構建信息區分開來,方便各司其職,符合單一職責設計原則。markdown
Method
就是method_t
類型的指針;method_t
是對方法/函數的封裝(函數四要素:函數名、返回值、參數、函數體)。typedef struct method_t *Method;
複製代碼
struct method_t { SEL name; // 方法名 const char *types; // 編碼(返回值類型、參數類型) IMP imp; // 方法的地址/實現 }; 複製代碼
selector
的指針,表明方法/函數名;typedef struct objc_selector *SEL; 複製代碼
SEL sel1 = @selector(selector); SEL sel2 = sel_registerName("selector"); SEL sel3 = NSSelectorFromString(@"selector"); 複製代碼
char *string1 = sel_getName(sel1); NSString *string2 = NSStringFromSelector(sel1); 複製代碼
method_t
實際上至關於在 SEL 和 IMP 之間作了一個映射。#if !OBJC_OLD_DISPATCH_PROTOTYPES typedef void (*IMP)(void /* id, SEL, ... */ ); #else typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...); #endif 複製代碼
runtime
的技術,把一個方法的返回值類型、參數類型經過字符串的形式描述;@encode()
指令能夠將類型轉換爲 Type Encodings 字符串編碼, 如@encode(int)
=i
;OC
方法都有兩個隱式參數,方法調用者(id)self
和方法名(SEL) _cmd
,因此咱們才能在方法中使用self
和_cmd
;-(void)test
,它的編碼爲「v16@0:8
」,能夠簡寫爲「v@:
」 v
:表明返回值類型爲 void @
:表明參數 1 類型爲 id :
:表明參數 2 類型爲 SEL 16
:表明全部參數所佔的總字節數 0
:表明參數 1 從第幾個字節開始存儲 8
:表明參數 2 從第幾個字節開始存儲runtime
的消息轉發中會使用到;