本次主要記錄幾個(gè)問(wèn)題
1、runtime是什么
2、method-swizzling使用及使用過(guò)程中的坑點(diǎn)
3、isKinkOfClass和isMemmberOfClass的使用
4、[self class]和[super class]的區(qū)別以及原理分析
5、runtime Associate方法的關(guān)聯(lián)對(duì)象,及什么時(shí)間釋放
6、__weak對(duì)象的存儲(chǔ)及釋放
1、runtime是什么
這個(gè)就說(shuō)的比較寬泛一些,runtime是一套由C & C++編寫(xiě)的一套api,給我們OC提供運(yùn)行時(shí)的功能,研究runtime對(duì)其他底層的學(xué)習(xí)會(huì)有一個(gè)比較有利的幫助
2、method-swizzling使用及使用過(guò)程中的坑點(diǎn)
Class class = [self class];
// 原方法名和替換方法名
SEL originalSelector = @selector(isEqualToString:);
SEL swizzledSelector = @selector(swizzle_IsEqualToString:);
// 原方法和替換方法
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
// 如果當(dāng)前類沒(méi)有原方法的實(shí)現(xiàn)IMP,先調(diào)用class_addMethod來(lái)給原方法添加實(shí)現(xiàn)
BOOL didAddMethod = class_addMethod(class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {// 添加方法實(shí)現(xiàn)IMP成功后,替換方法實(shí)現(xiàn)
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else { // 有原方法,交換兩個(gè)方法的實(shí)現(xiàn)
method_exchangeImplementations(originalMethod, swizzledMethod);
}
1、方法交換,一般我們是在+ (void)load方法中進(jìn)行方法交換,為什么
1)執(zhí)行比較早,在main函數(shù)之前就已經(jīng)執(zhí)行
2)自動(dòng)執(zhí)行,不需要手動(dòng)調(diào)用,這里的調(diào)用 不是通過(guò)objc_msgSend調(diào)用而是直接通過(guò)指針找到相應(yīng)的imp調(diào)用
3)唯一性,調(diào)用順序?yàn)?先 父類 -> 子類 -> 分類
》這里有一個(gè)優(yōu)化點(diǎn),App啟動(dòng)的時(shí)候 main函數(shù)之前 若是當(dāng)前類 實(shí)現(xiàn)了load方法 就會(huì)調(diào)用該方法,若是load中執(zhí)行了耗時(shí)操作,就會(huì)延長(zhǎng)App的啟動(dòng)時(shí)間,所以 我們減少App的啟動(dòng)時(shí)間的一個(gè)優(yōu)化點(diǎn)就是 不要在load方法中進(jìn)行耗時(shí)操作,也不要隨便重寫(xiě)load方法
》還有一個(gè)面試題是 load方法是否可以被交換即被hook
答案是可以的,但是成本會(huì)比較高
我們看load方法的調(diào)用堆棧

動(dòng)態(tài)鏈接器dyld,完成對(duì)二進(jìn)制文件(動(dòng)態(tài)庫(kù)、可執(zhí)行文件)的初始化后通過(guò)回調(diào)函數(shù)_dyld_objc_notify_register,調(diào)用load_images和call_load_method實(shí)現(xiàn)load方法的調(diào)用,
void
load_images(const char *path __unused, const struct mach_header *mh)
{
// Return without taking locks if there are no +load methods here.
if (!hasLoadMethods((const headerType *)mh)) return;
recursive_mutex_locker_t lock(loadMethodLock);
// Discover load methods
{
mutex_locker_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
call_load_methods();
}
load_images通過(guò)prepare_load_methods將所有類的load方法加入到list中,包括父類和分類,這里記錄的是所有有l(wèi)oad方法的類 和 load方法的imp,之后通過(guò)call_load_methods調(diào)用所有的load方法
有上述問(wèn)題 若是我們需要hook A的load方法 必須在A的load方法被加載進(jìn)入load方法列表之前進(jìn)行hook,即我們把hook A的load方法的代碼放在B中,然后保證B動(dòng)態(tài)庫(kù)在A之前被鏈接,這樣就可以進(jìn)行hook了
isKinkOfClass和isMemmberOfClass的使用

這里首先我們看一下下面代碼的打印,先看實(shí)例方法,這個(gè)比較簡(jiǎn)單
- (void)test4 {
NSObject *object = [[NSObject alloc] init];
Person *person = [[Person alloc] init];
bool o1 = [object isKindOfClass:[NSObject class]];
bool o2 = [object isMemberOfClass:[NSObject class]];
bool p1 = [person isKindOfClass:[Person class]];
bool p2 = [person isMemberOfClass:[Person class]];
// 1-1-1-1
NSLog(@"%d-%d-%d-%d",o1,o2,p1,p2);
}
-isKindOfClass
- (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
我們可以看到,對(duì)象方法的 for循環(huán) 初始值 變成了 [self class],也就是從當(dāng)前類開(kāi)始找superclass繼承鏈。
所以 [(id)[NSObject alloc] isKindOfClass:[NSObject class]] 和 [(id)[DZPerson alloc] isKindOfClass:[DZPerson class]] 都為 YES
-isMemberOfClass
- (BOOL)isMemberOfClass:(Class)cls {
return [self class] == cls;
}
- -isMemberOfClass 對(duì)象方法更是簡(jiǎn)單了,直接就是判斷當(dāng)前類和傳入類是否相等。
- [(id)[NSObject alloc] isMemberOfClass:[NSObject class]] 和 [(id)[DZPerson alloc] isMemberOfClass:[DZPerson class]] 自然都是 YES。
再看類方法
- (void)test5 {
Class object = [NSObject class];
Class person = [Person class];
bool o1 = [object isKindOfClass:[NSObject class]];
bool o2 = [object isMemberOfClass:[NSObject class]];
bool p1 = [person isKindOfClass:[Person class]];
bool p2 = [person isMemberOfClass:[Person class]];
// 1-0-0-0
NSLog(@"===class=%d-%d-%d-%d",o1,o2,p1,p2);
}
為什么呢?我們看一下isKindOfClass的源碼
+ (BOOL)isKindOfClass:(Class)cls {
for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
if (tcls == cls) return YES;
}
return NO;
}
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
我們可以看出
-
Class tcls = object_getClass((id)self);
從源碼可以看到,self 是類本身,object_getClass((id)self) 則是獲取 isa,而 isa 是指向元類的,所以 tcls 實(shí)際上是當(dāng)前類的元類。
for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass)
- for循環(huán)實(shí)際上就是從當(dāng)前類的元類開(kāi)始,沿著繼承鏈中的 superclass 一直向上循環(huán),在如下 isa指向圖 中標(biāo)注部分,NSObject元類 的父類是 NSObject。所以在第二次循環(huán)的時(shí)候,NSObject元類 的 superclass 是本身NSObject。
- 但是 DZPerson元類 的繼承鏈?zhǔn)荄ZPerson元類 -> NSObject元類 -> NSObject,所以在 DZPerson元類 的繼承鏈上永遠(yuǎn)不會(huì)有自身DZPerson。
- 因此 [(id)[NSObject class] isKindOfClass:[NSObject class]] = YES ,而 [(id)[DZPerson class] isKindOfClass:[DZPerson class]] == NO。
再看 isMemberOfClass的源碼
+ (BOOL)isMemberOfClass:(Class)cls {
return object_getClass((id)self) == cls;
}
- 從源碼中可以看到,代碼是直接判斷當(dāng)前類的元類是否等于傳入類。
- 所以 [(id)[NSObject class] isMemberOfClass:[NSObject class]] 和 [(id)[DZPerson class] isMemberOfClass:[DZPerson class]]中,NSObject元類 不等于 NSObject,DZPerson元類 也不等于 DZPerson,結(jié)果自然都是 NO。
[self class]和[super class]的區(qū)別以及原理分析
[self class]
- (Class)class {
return object_getClass(self);
}
///
Class object_getClass(id obj)
{
if (obj) return obj->getIsa();
else return Nil;
}
也就是獲取當(dāng)前類的isa指向 實(shí)例的isa指向類
[super class]
本質(zhì)上是調(diào)用objc_msgSendSuper
OBJC_EXPORT void
objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )
OBJC_AVAILABLE(10.0, 2.0, 9.0, 1.0, 2.0);
第一個(gè)參數(shù)為一個(gè)objc_super的結(jié)構(gòu)體
struct objc_super {
__unsafe_unretained _Nonnull id receiver;
__unsafe_unretained _Nonnull Class super_class;
};
那么[super class]就等價(jià)于
struct objc_super lg_super = {
//我們這里研究對(duì)象為當(dāng)前對(duì)象所以消息接收這位self
self,
class_getSuperclass([self class]),
};
objc_msgSendSuper(&lg_super,@selector(class))
消息接受者 還是self 即 還是會(huì)打印實(shí)例的類
[self class] 就是發(fā)送消息objc_msgSend,消息接受者是 self 方法編號(hào):class
[super class] 本質(zhì)就是objc_msgSendSuper, 消息的接受者還是 self 方法編號(hào):class 只是objc_msgSendSuper 會(huì)更快 直接跳過(guò) self 的查找
不能向編譯后的類中添加實(shí)例變量,但是可以向運(yùn)行時(shí)創(chuàng)建的類中添加實(shí)例變量
- 類編譯后只讀結(jié)構(gòu)體class_ro_t就被確定了,運(yùn)行時(shí)不可修改
- ro結(jié)構(gòu)體中的iver_list也是不可修改的,并且instanceSize決定了創(chuàng)建對(duì)象時(shí)需要的空間大小
- 運(yùn)行時(shí)添加實(shí)例變量 必須在objc_allocateClassPair和objc_registerClassPair之間調(diào)用class_addIver
runtime Associate方法的關(guān)聯(lián)對(duì)象,及什么時(shí)間釋放
- 需要調(diào)用objc_setAssociatedObject,詢問(wèn)是否存在全局的AssociationsManager有就獲取,沒(méi)有就創(chuàng)建,然后跟拒當(dāng)前類獲取 ObjectAssociationMap,沒(méi)有就創(chuàng)建一個(gè),存入我們的關(guān)聯(lián)對(duì)象
- _object_get_associative_reference,獲取也是同樣的邏輯 全局AssociationsManager,根據(jù)當(dāng)前類獲取ObjectAssociationMap,再根據(jù)關(guān)聯(lián)對(duì)象的key獲取值
- 刪除關(guān)聯(lián)對(duì)象 ,在主類dealloc時(shí)調(diào)用析構(gòu)函數(shù) objc_destructInstance,然后看當(dāng)前類是否存在析構(gòu),存在的話調(diào)用_object_remove_assocations,刪除析構(gòu)
__weak對(duì)象的存儲(chǔ)及釋放

- 從全局的SideTables中,利用對(duì)象本身的地址取得該對(duì)象的弱引用表weak_table
- 如果有分配新值,則檢查新值對(duì)應(yīng)的類是否進(jìn)行過(guò)初始化,如果沒(méi)有則就地初始化weak_register_no_lock
- 若是有舊值,則需要把舊值對(duì)應(yīng)的弱引用表進(jìn)行注銷weak_unregister_no_lock
- 將新值注冊(cè)到對(duì)應(yīng)的弱引用表中,將isa.weakly_referenced設(shè)置為true,表示該類有弱引用變量,釋放時(shí)需要清空弱引用表
鏈接弱引用表
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;// 被引用的對(duì)象
objc_object **referrer = (objc_object **)referrer_id;// 弱引用變量
if (!referent || referent->isTaggedPointer()) return referent_id;
// 確保弱引用對(duì)象是否可行
// ensure that the referenced object is viable
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) {
return nil;
}
deallocating =
! (*allowsWeakReference)(referent, SEL_allowsWeakReference);
}
// 如果正在釋放中,則根據(jù) crashIfDeallocating 判斷是否觸發(fā) crash
if (deallocating) {
if (crashIfDeallocating) {
_objc_fatal("Cannot form weak reference to instance (%p) of "
"class %s. It is possible that this object was "
"over-released, or is in the process of deallocation.",
(void*)referent, object_getClassName((id)referent));
} else {
return nil;
}
}
// now remember it and where it is being stored
// 每個(gè)對(duì)象對(duì)應(yīng)的一個(gè)弱引用記錄
weak_entry_t *entry;
// 如果當(dāng)前表中有該對(duì)象的記錄則直接加入該 weak 表中對(duì)應(yīng)記錄
if ((entry = weak_entry_for_referent(weak_table, referent))) {
append_referrer(entry, referrer);
}
else {
// 沒(méi)有在 weak 表中找到對(duì)應(yīng)記錄,則新建一個(gè)記錄
weak_entry_t new_entry(referent, referrer);
// 查看是否需要擴(kuò)容
weak_grow_maybe(weak_table);
// 將記錄插入 weak 表中
weak_entry_insert(weak_table, &new_entry);
}
// Do not set *referrer. objc_storeWeak() requires that the
// value not change.
return referent_id;
}
上述代碼主要功能如下:
- 判斷被指向?qū)ο笫欠窨尚?,也就是判斷其是否正在釋放,并且?huì)根據(jù)crashIfDeallocating判斷是否觸發(fā)crash。
- 在weak_table中檢測(cè)是否有被指向?qū)ο蟮膃ntry,如果有的話,直接將該弱引用變量指針加入到該entry中
- 如果沒(méi)有找到對(duì)應(yīng)的entry,新建一個(gè)entry,并將弱引用變量指針地址加入entry,同時(shí)檢查weaktable是否擴(kuò)容。
移除
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;
// 指向?qū)ο鬄榭罩苯臃祷? if (!referent) return;
// 在weak表中查找
if ((entry = weak_entry_for_referent(weak_table, referent))) {
// 找到相應(yīng)記錄后,將該引用從記錄中移除。
remove_referrer(entry, referrer);
// 移除后檢查該記錄是否為空
bool empty = true;
if (entry->out_of_line() && entry->num_refs != 0) {
// 不為空 將標(biāo)記記錄為false
empty = false;
}
else {
// 對(duì)比到記錄的每一行
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i]) {
empty = false;
break;
}
}
}
// 如果當(dāng)前記錄為空則移除記錄
if (empty) {
weak_entry_remove(weak_table, entry);
}
}
// Do not set *referrer = nil. objc_storeWeak() requires that the
// value not change.
}
這段代碼的主要流程
- 從weak_table中根據(jù)找到被引用對(duì)象對(duì)應(yīng)的entry,然后將弱引用變量指針referrer從entry中移除。
- 移除弱引用變量指針referrer之后,檢查entry是否為空,如果為空將其從weak_table中移除
static void weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry)
{
// 釋放 entry 中的所有弱引用
if (entry->out_of_line()) free(entry->referrers);
// 置空指針
bzero(entry, sizeof(*entry));
// 更新 weak_table 對(duì)象數(shù)量,并檢查是否可以縮減表容量
weak_table->num_entries--;
weak_compact_maybe(weak_table);
}
- 釋放entry和其中的弱引用變量。
- 更新 weak_table 對(duì)象數(shù)量,并檢查是否可以縮減表容量
entry 和 referrer
entry以及比較熟悉了,一個(gè)對(duì)象的弱引用記錄,referrer則是代表弱引用變量,每次被弱引用時(shí),都會(huì)將弱引用變量指針referrer加入entry中,而當(dāng)原對(duì)象被釋放時(shí),會(huì)將entry清空并移除
從entry移除referrer的步驟:
out_of_line為false時(shí),從有序數(shù)組inline_referrers中查找并移除。
out_of_line為true時(shí),從哈希表中查找并移除
dealloc
當(dāng)被引用的對(duì)象被釋放后,會(huì)去檢查isa.weakly_referenced標(biāo)志位,每個(gè)被弱引用的對(duì)象weakly_referenced標(biāo)志位都為true。
NEVER_INLINE void
objc_object::clearDeallocating_slow()
{
assert(isa.nonpointer && (isa.weakly_referenced || isa.has_sidetable_rc));
// 根據(jù)指針獲取對(duì)應(yīng) Sidetable
SideTable& table = SideTables()[this];
table.lock();
if (isa.weakly_referenced) {
// 存在弱引用
weak_clear_no_lock(&table.weak_table, (id)this);
}
if (isa.has_sidetable_rc) {
table.refcnts.erase(this);
}
table.unlock();
}
從上面的代碼可以看出,在對(duì)象釋執(zhí)行dealloc函數(shù)時(shí),會(huì)檢查isa.weakly_referenced標(biāo)志位,然后判斷是否要清理weak_table中的entry。
這最后還是走到了前面的remove。