日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Objective-C Category 的实现原理

發布時間:2023/12/18 编程问答 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Objective-C Category 的实现原理 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

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

使用場景

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

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

其中,第 1 個是最典型的使用場景,應用最廣泛。

注:Category 有一個非常容易誤用的場景,那就是用 Category 來覆寫父類或主類的方法。雖然目前 Objective-C 是允許這么做的,但是這種使用場景是非常不推薦的。使用
Category 來覆寫方法有很多缺點,比如不能覆寫 Category 中的方法、無法調用主類中的原始實現等,且很容易造成無法預估的行為。

實現原理

我們知道,無論我們有沒有主動引入 Category 的頭文件,Category 中的方法都會被添加進主類中。我們可以通過 - performSelector: 等方式對 Category 中的相應方法進行調用,之所以需要在調用的地方引入 Category 的頭文件,只是為了“照顧”編譯器同學的感受。

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

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 中的協議被同時整合到了主類和元類中。

我們注意到,不管是哪種情況,最終都是通過調用 static void remethodizeClass(Class cls) 函數來重新整理類的數據的。

static void remethodizeClass(Class cls) {...cls->nameForLogging(), isMeta ? "(meta)" : "");}// Update methods, properties, protocolsattachCategoryMethods(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)、properties 和 protocols 的值。進一步,我們通過 attachCategoryMethods 函數的源碼可以找到真正處理 Category 方法的 attachMethodLists 函數:

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 arrayfor (i = 0; i < oldCount; i++) {newLists[newCount++] = oldLists[i];}if (oldLists && oldLists != oldBuf) free(oldLists);// nil-terminatenewLists[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_lists 或 method_list 。通過探究這個處理過程,我們也印證了一個結論,那就是主類中的方法和 Category 中的方法在 runtime 看來并沒有區別,它們是被同等對待的,都保存在主類的方法列表中。

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

// 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 == 1method_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 的方式提供強大功能,比如 AFNetworking 、ReactiveCocoa 、 SDWebImage 等。但是凡事有利必有弊,正因為 Category 非常強大,所以一旦誤用就很可能會造成非常嚴重的后果。比如覆寫系統類的方法,這是 iOS 開發新手經常會犯的一個錯誤,不管在任何情況下,切記一定不要這么做,No zuo no die 。

原文鏈接:http://blog.leichunfeng.com/blog/2015/05/18/objective-c-category-implementation-principle/

總結

以上是生活随笔為你收集整理的Objective-C Category 的实现原理的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。