這是收集到的一些關於OC runtime的blog。文中的技術要點爲各個blog的技術點的結論總結,拋卻文中源碼和解析過程,不想看原文的可以直接看要點乾貨。
關於category
深入理解Objective-C:Category 技術要點:
一 category是Objective-C 2.0之後添加的語言特性,category的主要作用:
- 爲已經存在的類添加方法
- 把類的實現分開在幾個不同的category文件裏面,好處:a)可以減少單個文件的體積 b)可以把不同的功能組織到不同的category裏 c)可以由多個開發者共同完成一個類 d)可以按需加載想要的category 等等
- 聲明私有方法
- 模擬多繼承
- 把framework的私有方法公開
二 與extension對比
- extension 在編譯器決議,一般用來隱藏類的私有信息,你必須有一個類的源碼才能爲一個類添加extension,所以你無法爲系統的類 比如NSString添加extension。而category在運行期決議,可以向任何類添加方法。
- extension可以添加實例變量,而category是無法添加實例變量的(因爲在運行期,對象的內存佈局已經確定,如果添加實例變量就會破壞類的內部佈局,這對編譯型語言來說是災難性的)。
三 被category覆蓋掉的 類中的original method
- category的方法沒有“完全替換掉”原來類已經有的方法,而是將它放到了category的方法的後面。selector與方法的實現IMP是一個map結構的,map中的key就是selector,value就是IMP,可以理解有多個同名方法的map,在消息派發時,一旦匹配到就不在向後查找,所以從表現上來說,category中方法好像覆蓋了類中的original method。 正因爲這樣,我們仍然可以調用到類中的original method,只要順着方法列表找到最後一個對應名字的方法,就可以調用原來類的方法(這是根據blog中的Code封裝的一個方法):
- (IMP)ed_getOriginalMethodInClass:(Class)targetClass withSelector:(SEL)aSeclector
{
if (targetClass) {
unsigned int methodCount;
Method *methodList = class_copyMethodList(targetClass, &methodCount);
IMP lastImp = NULL;
SEL lastSel = NULL;
NSString *targetSELString = NSStringFromSelector(aSelector);
for (NSUInteger i = 0; i < methodCount; i++) {
Method method = methodList[i];
NSString *methodName = [NSString stringWithCString:sel_getName(method_getName(method))
encoding:NSUTF8StringEncoding];
if ([targetSELString isEqualToString:methodName]) {
lastImp = method_getImplementation(method);
lastSel = method_getName(method);
}
}
free(methodList);
return lastIMP;
}
return NULL;
}
四 category和+load方法
- 類中和category中均可以實現+load方法
- 執行順序: 先 類中,後 category中,多個category都實現了同名方法,那麼會按照編譯順序執行。
小技巧:在Xcode中點擊Edit Scheme
,添加如下兩個環境變量(可以在執行load方法以及加載category的時候打印log信息,更多的環境變量選項可參見objc-private.h):
設置如圖一所示的環境變量就可以在啓動應用的時候打印加載信息,文件結構如圖二所示,分別有圖三和圖四兩種文件順序,那麼編譯使調用+load方法的順序就不同,打印信息就不上了。
五 category和關聯對象
- 現在在category裏面是無法爲category添加實例變量的,雖然從原文blog的源碼解析中可以看出apple有在category中實現添加property的意圖,但是還沒有實現。
- 向category中添加屬性,只能通過關聯屬性來做。舉例代碼如下:
@implementation MyClass (Category1)
+ (void)load
{
NSLog(@"%@",@"load in Category1");
}
- (void)setName:(NSString *)name
{
objc_setAssociatedObject(self,
"name",
name,
OBJC_ASSOCIATION_COPY);
}
- (NSString*)name
{
NSString *nameObject = objc_getAssociatedObject(self, "name");
return nameObject;
}
@end
- 所有的關聯對象都由AssociationsManager管理,而在對象的銷燬邏輯裏面,runtime的銷燬對象函數objc_destructInstance裏面會判斷這個對象有沒有關聯對象,如果有,會調用_object_remove_assocations做關聯對象的清理工作。
關於方法緩存
深入理解Objective-C:方法緩存 技術要點:
一 方法緩存
- 類class的底層定義都是struct,類的定義裏就有cache字段,類的所有緩存都存在metaclass上,所以每個類都只有一份方法緩存,而不是每一個類的object都保存一份。
struct _class_t {
struct _class_t *isa;
struct _class_t *superclass;
void *cache;
void *vtable;
struct _class_ro_t *ro;
};
- 從父類取到的方法,也會存在類本身的方法緩存裏。而當用一個父類對象去調用那個方法的時候,也會在父類的metaclass裏緩存一份。
- 類的方法緩存大小沒有限制,爲了防止快速無限增大,apple的實現方法會使緩存的大小增速慢一點,但是確實是沒有上限的(具體可見原文)。
- 爲什麼類的方法列表不直接做成散列表,而是使用list+單獨緩存?
原文給出的可能原因:
- 散列表是沒有順序的,Objective-C的方法列表是一個list,是有順序的;Objective-C在查找方法的時候會順着list依次尋找,並且category的方法在原始方法list的前面,需要先被找到,如果直接用hash存方法,方法的順序就沒法保證。
- list的方法還保存了除了selector和imp之外其他很多屬性
- 散列表是有空槽的,會浪費空間