Objective-C Category 的實現原理[轉]

對設計模式有必定了解的朋友應該據說過裝飾模式,Objective-C 中的 Category 就是對裝飾模式的一種具體實現。它的主要做用是在不改變原有類的前提下,動態地給這個類添加一些方法。在 Objective-C 中的具體體現爲:實例(類)方法、屬性和協議。是的,在 Objective-C 中能夠用 Category 來實現協議。本文將結合 runtime(我下載的是當前的最新版本 objc4-646.tar.gz) 的源碼來探究它實現的原理。html

使用場景

根據蘋果官方文檔對 Category 的描述,它的使用場景主要有三個:ios

  1. 給現有的類添加方法;
  2. 將一個類的實現拆分紅多個獨立的源文件;
  3. 聲明私有的方法。

其中,第 1 個是最典型的使用場景,應用最普遍。git

:Category 有一個很是容易誤用的場景,那就是用 Category 來覆寫父類或主類的方法。雖然目前 Objective-C 是容許這麼作的,可是這種使用場景是很是不推薦的。使用 Category 來覆寫方法有不少缺點,好比不能覆寫 Category 中的方法、沒法調用主類中的原始實現等,且很容易形成沒法預估的行爲。github

實現原理

咱們知道,不管咱們有沒有主動引入 Category 的頭文件,Category 中的方法都會被添加進主類中。咱們能夠經過 - performSelector: 等方式對 Category 中的相應方法進行調用,之因此須要在調用的地方引入 Category 的頭文件,只是爲了「照顧」編譯器同窗的感覺。objective-c

下面,咱們將結合 runtime 的源碼探究下 Category 的實現原理。打開 runtime 源碼工程,在文件 objc-runtime-new.mm 中找到如下函數:設計模式

1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 
void _read_images(header_info **hList, uint32_t hCount) {  ...  _free_internal(resolvedFutureClasses);  }   // Discover categories.  for (EACH_HEADER) {  category_t **catlist =  _getObjc2CategoryList(hi, &count);  for (i = 0; i < count; i++) {  category_t *cat = catlist[i];  Class cls = remapClass(cat->cls);   if (!cls) {  // Category's target class is missing (probably weak-linked).  // Disavow any knowledge of this category.  catlist[i] = nil;  if (PrintConnecting) {  _objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "  "missing weak-linked target class",  cat->name, cat);  }  continue;  }   // Process this category.  // First, register the category with its target class.  // Then, rebuild the class's method lists (etc) if  // the class is realized.  BOOL classExists = NO;  if (cat->instanceMethods || cat->protocols  || cat->instanceProperties)  {  addUnattachedCategoryForClass(cat, cls, hi);  if (cls->isRealized()) {  remethodizeClass(cls);  classExists = YES;  }  if (PrintConnecting) { _objc_inform("CLASS: found category -%s(%s) %s", cls->nameForLogging(), cat->name, classExists ? "on existing class" : ""); } } if (cat->classMethods || cat->protocols /* || cat->classProperties */) { addUnattachedCategoryForClass(cat, cls->ISA(), hi); if (cls->ISA()->isRealized()) { remethodizeClass(cls->ISA()); } if (PrintConnecting) { _objc_inform("CLASS: found category +%s(%s)", cls->nameForLogging(), cat->name); } } } } // Category discovery MUST BE LAST to avoid potential races // when other threads call the new category code before // this thread finishes its fixups. // +load handled by prepare_load_methods() ... } 

從第 27-58 行的關鍵代碼,咱們能夠知道在這個函數中對 Category 作了以下處理:數組

  1. 將 Category 和它的主類(或元類)註冊到哈希表中;
  2. 若是主類(或元類)已實現,那麼重建它的方法列表。

在這裏分了兩種狀況進行處理:Category 中的實例方法和屬性被整合到主類中;而類方法則被整合到元類中(關於對象、類和元類的更多細節,能夠參考我前面的博文《Objective-C 對象模型》)。另外,對協議的處理比較特殊,Category 中的協議被同時整合到了主類和元類中。app

咱們注意到,不論是哪一種狀況,最終都是經過調用 static void remethodizeClass(Class cls) 函數來從新整理類的數據的。函數

1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 
static void remethodizeClass(Class cls) {  ...  cls->nameForLogging(), isMeta ? "(meta)" : "");  }   // Update methods, properties, protocols   attachCategoryMethods(cls, cats, YES);   newproperties = buildPropertyList(nil, cats, isMeta);  if (newproperties) {  newproperties->next = cls->data()->properties;  cls->data()->properties = newproperties;  }   newprotos = buildProtocolList(cats, nil, cls->data()->protocols);  if (cls->data()->protocols && cls->data()->protocols != newprotos) {  _free_internal(cls->data()->protocols);  }  cls->data()->protocols = newprotos;   _free_internal(cats);  } } 

這個函數的主要做用是將 Category 中的方法、屬性和協議整合到類(主類或元類)中,更新類的數據字段 data()method_lists(或 method_list)propertiesprotocols 的值。進一步,咱們經過 attachCategoryMethods 函數的源碼能夠找到真正處理 Category 方法的 attachMethodLists 函數:ui

1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 
static void attachMethodLists(Class cls, method_list_t **addedLists, int addedCount,  bool baseMethods, bool methodsFromBundle,  bool flushCaches) {  ...  newLists[newCount++] = mlist;  }   // Copy old methods to the method list array  for (i = 0; i < oldCount; i++) {  newLists[newCount++] = oldLists[i];  }  if (oldLists && oldLists != oldBuf) free(oldLists);   // nil-terminate  newLists[newCount] = nil;   if (newCount > 1) {  assert(newLists != newBuf);  cls->data()->method_lists = newLists;  cls->setInfo(RW_METHOD_ARRAY);  } else {  assert(newLists == newBuf);  cls->data()->method_list = newLists[0];  assert(!(cls->data()->flags & RW_METHOD_ARRAY));  } } 

這個函數的代碼量看上去比較多,可是咱們並不難理解它的目的。它的主要做用就是將類中的舊有方法和 Category 中新添加的方法整合成一個新的方法列表,並賦值給 method_listsmethod_list 。經過探究這個處理過程,咱們也印證了一個結論,那就是主類中的方法和 Category 中的方法在 runtime 看來並無區別,它們是被同等對待的,都保存在主類的方法列表中。

不過,類的方法列表字段有一點特殊,它的結構是聯合體,method_listsmethod_list 共用同一塊內存地址。當 newCount 的個數大於 1 時,使用 method_lists 來保存 newLists ,並將方法列表的標誌位置爲 RW_METHOD_ARRAY ,此時類的方法列表字段是 method_list_t 類型的指針數組;不然,使用 method_list 來保存 newLists ,並將方法列表的標誌位置空,此時類的方法列表字段是 method_list_t 類型的指針。

1
2 3 4 5 6 7 
// class's method list is an array of method lists #define RW_METHOD_ARRAY (1<<20)  union {  method_list_t **method_lists; // RW_METHOD_ARRAY == 1  method_list_t *method_list; // RW_METHOD_ARRAY == 0 }; 

看過我上一篇博文《Objective-C +load vs +initialize》的 朋友可能已經有所察覺了。咱們注意到 runtime 對 Category 中方法的處理過程並無對 +load 方法進行什麼特殊地處理。所以,嚴格意義上講 Category 中的 +load 方法跟普通方法同樣也會對主類中的 +load 方法形成覆蓋,只不過 runtime 在自動調用主類和 Category 中的 +load 方法時是直接使用各自方法的指針進行調用的。因此纔會使咱們以爲主類和 Category 中的 +load 方法好像互不影響同樣。所以,當咱們手動給主類發送 +load 消息時,調用的一直會是分類中的 +load 方法,you should give it a try yourself 。

總結

Category 是 Objective-C 中很是強大的技術之一,使用得當的話能夠給咱們的開發帶來極大的便利。不少著名的開源庫或多或少都會經過給系統類添加 Category 的方式提供強大功能,好比 AFNetworkingReactiveCocoaSDWebImage 等。可是凡事有利必有弊,正由於 Category 很是強大,因此一旦誤用就極可能會形成很是嚴重的後果。好比覆寫系統類的方法,這是 iOS 開發新手常常會犯的一個錯誤,無論在任何狀況下,切記必定不要這麼作,No zuo no die 。

參考連接

https://developer.apple.com/library/ios/documentation/General/Conceptual/DevPedia-CocoaCore/Category.html#//apple_ref/doc/uid/TP40008195-CH5-SW1 http://stackoverflow.com/questions/5272451/overriding-methods-using-categories-in-objective-c

相關文章
相關標籤/搜索