深刻淺出 Runtime(一):初識
深刻淺出 Runtime(二):數據結構
深刻淺出 Runtime(三):消息機制
深刻淺出 Runtime(四):super 的本質
深刻淺出 Runtime(五):具體應用
深刻淺出 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; // 函數的內存地址
};
複製代碼
//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; // 裏面存儲的值是引用計數 retainCount - 1
uintptr_t extra_rc : 19; // 若是爲1,表明引用計數過大沒法存儲在 isa 中,那麼超出的引用計數會存儲在一個叫 SideTable 結構體的 RefCountMap(引用計數表)哈希表中
# 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
複製代碼
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
的消息轉發中會使用到;