收集的Objective-C runtime博客及知識點總結

這是收集到的一些關於OC runtime的blog。文中的技術要點爲各個blog的技術點的結論總結,拋卻文中源碼和解析過程,不想看原文的可以直接看要點乾貨。

關於category

深入理解Objective-C:Category 技術要點:

一 category是Objective-C 2.0之後添加的語言特性,category的主要作用:

  1. 爲已經存在的類添加方法
  2. 把類的實現分開在幾個不同的category文件裏面,好處:a)可以減少單個文件的體積 b)可以把不同的功能組織到不同的category裏 c)可以由多個開發者共同完成一個類 d)可以按需加載想要的category 等等
  3. 聲明私有方法
  4. 模擬多繼承
  5. 把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):
設置環境變量
文件結構
編譯順序1
編譯順序2

設置如圖一所示的環境變量就可以在啓動應用的時候打印加載信息,文件結構如圖二所示,分別有圖三和圖四兩種文件順序,那麼編譯使調用+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之外其他很多屬性
  • 散列表是有空槽的,會浪費空間

關於runtime

Objective-C特性:Runtime, 這篇沒有技術要點,要自己通讀下來。推薦。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章