在簡書看到大牛的知識點,發(fā)現(xiàn)很多知識點自己一知半解,能做項目但理論不夠扎實,默默地去百度總結(jié)一下.放到這里和大家交流交流.
如有侵權(quán),告知即刪!
01.objc在向一個對象發(fā)送消息時,發(fā)生了什么?
objective-c 的 Runtime 鑄就了它動態(tài)語言的特性,這些深層次的知識雖然平時寫代碼用的少一些,但是卻是每個 Objc 程序員需要了解的。
Objc Runtime使得C具有了面向?qū)ο竽芰Γ诔绦蜻\行時創(chuàng)建,檢查,修改類、對象和它們的方法。可以使用runtime的一系列方法實現(xiàn)。
附上OC中一個類的底層數(shù)據(jù)結(jié)構(gòu)
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY; //isa指針指向Meta Class,因為Objc的類的本身也是一個Object,為了處理這個關(guān)系,r untime就創(chuàng)造了Meta Class,當給類發(fā)送[NSObject alloc]這樣消息時,實際上是把這個消息發(fā)給了Class Object
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE; // 父類
const char *name OBJC2_UNAVAILABLE; // 類名
long version OBJC2_UNAVAILABLE; // 類的版本信息,默認為0
long info OBJC2_UNAVAILABLE; // 類信息,供運行期使用的一些位標識
long instance_size OBJC2_UNAVAILABLE; // 該類的實例變量大小
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; // 該類的成員變量鏈表
struct objc_method_list **methodLists OBJC2_UNAVAILABLE; // 方法定義的鏈表
struct objc_cache *cache OBJC2_UNAVAILABLE; // 方法緩存,對象接到一個消息會根據(jù)isa指針查找消息對象,這時會在method Lists中遍歷,如果cache了,常用的方法調(diào)用時就能夠提高調(diào)用的效率。
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 協(xié)議鏈表
#endif
} OBJC2_UNAVAILABLE;
OC中一個類的對象實例的[數(shù)據(jù)結(jié)構(gòu)],就是定義了一個
typedef struct objc_class *Class;
/// Represents an instance of a class.
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
/// A pointer to an instance of a class.
typedef struct objc_object *id;
向object發(fā)送消息時,Runtime庫會根據(jù)object的isa指針找到這個實例object所屬于的類,然后在類的方法列表以及父類方法列表尋找對應(yīng)的方法運行。id是一個objc_object結(jié)構(gòu)類型的指針,這個類型的對象能夠轉(zhuǎn)換成任何一種對象。
然后再來看看消息發(fā)送的函數(shù):objc_msgSend函數(shù)
在引言中已經(jīng)對objc_msgSend進行了一點介紹,看起來像是objc_msgSend返回了數(shù)據(jù),其實objc_msgSend從不返回數(shù)據(jù)而是你的方法被調(diào)用后返回了數(shù)據(jù)。下面詳細敘述下消息發(fā)送步驟:
1.檢測這個 selector 是不是要忽略的。比如 Mac OS X 開發(fā),有了垃圾回收就不理會 retain,release 這些函數(shù)了。
2.檢測這個 target 是不是 nil 對象。ObjC 的特性是允許對一個 nil 對象執(zhí)行任何一個方法不會 Crash,因為會被忽略掉。
3.如果上面兩個都過了,那就開始查找這個類的 IMP,先從 cache 里面找,完了 找得到就跳到對應(yīng)的函數(shù)去執(zhí)行。
4.如果 cache 找不到就找一下方法分發(fā)表。
5.如果分發(fā)表找不到就到超類的分發(fā)表去找,一直找,直到找到NSObject類為止。
6.如果還找不到就要開始進入動態(tài)方法解析了,后面會提到。
02.什么時候會報unrecognized selector錯誤?iOS有哪些機制來避免走到這一步?
當調(diào)用對象的某個方法的時候, 如果在當前類中沒有找到此方法, 那么就到當前類的父類中去尋找, 如果在父類中沒有找到, 那么就去父類的父類中去尋找, 一直找到 NSObject 都沒有這個方法, 就會報 Unrecognized selector 的異常.
但是在這之前, objc 的運行時會給出三次拯救程序崩潰的機會.
第一次: 動態(tài)添加一個新方法并執(zhí)行的機會
[objc]
+ (bool)resolveInstanceMethod:(sel)sel{
}
當系統(tǒng)第一次找不到某個方法的時候, 會自動調(diào)用這個方法, 用來給程序添加一個新方法并執(zhí)行的機會.
第二次: 當系統(tǒng)調(diào)用上一個方法后未能實現(xiàn)添加新的方法, 則系統(tǒng)會再來調(diào)用下面的這個方法, 這個方法是系統(tǒng)提供的一個將 SEL 轉(zhuǎn)給其他對象的機會
[objc]
- (id)forwardingTargetForselector:(sel)aselctor{
}
第三次: 當 forwardingTargetForselector 返回的 nil 或者 self 時, 會進入到這個方法, 這個方法是拯救程序的最后一步.
這個方法用來返回一個方法簽名, 在由后面的 forwardInvocation: 去執(zhí)行
[objc]
- (NSMethodSigature *)methodSignatrueForseletor:(sel)aselector{
}
如果上面的方法不返回 nil, 則會來到這個方法里具體執(zhí)行
[objc]
-(void)forwardInvocation:(NSInvocation *)anInvocation{
在這里會調(diào)用自己對象的其他方法, 也可以調(diào)用其他函數(shù)
甚至還可以調(diào)用多個不同對象的多個方法
}
03.runtime如何實現(xiàn)weak變量的自動置nil?
要實現(xiàn) weak 屬性,首先要搞清楚 weak 屬性的特點:
weak 此特質(zhì)表明該屬性定義了一種“非擁有關(guān)系” (nonowning relationship)。為這種屬性設(shè)置新值時,設(shè)置方法既不保留新值,也不釋放舊值。此特質(zhì)同 assign 類似, 然而在屬性所指的對象遭到摧毀時,屬性值也會清空(nil out)。
那么 runtime 如何實現(xiàn) weak 變量的自動置nil?
runtime 對注冊的類, 會進行布局,對于 weak 對象會放入一個 hash 表中。 用 weak 指向的對象內(nèi)存地址作為 key,當此對象的引用計數(shù)為0的時候會 dealloc,假如 weak 指向的對象內(nèi)存地址是a,那么就會以a為鍵, 在這個 weak 表中搜索,找到所有以a為鍵的 weak 對象,從而設(shè)置為 nil。