簡述 Category 的實現(xiàn)原理
我們知道 Objective-C 通過 Runtime 運行時來實現(xiàn)動態(tài)語言這個特性,所有的類和對象,在 Runtime 中都是用結構體來表示的,Category 在 Runtime 中是用結構體 category_t 來表示的,下面是結構體 category_t 具體表示:
通過結構體 category_t 可以知道,在 Category 中我們可以增加實例方法、類方法、協(xié)議、屬性。我們這里簡述下 Category 的實現(xiàn)原理:
在編譯時期,會將分類中實現(xiàn)的方法生成一個結構體 method_list_t 、將聲明的屬性生成一個結構體 property_list_t ,然后通過這些結構體生成一個結構體 category_t 。
然后將結構體 category_t 保存下來
在運行時期,Runtime 會拿到編譯時期我們保存下來的結構體 category_t
然后將結構體 category_t 中的實例方法列表、協(xié)議列表、屬性列表添加到主類中
將結構體 category_t 中的類方法列表、協(xié)議列表添加到主類的 metaClass 中
這里需要注意的是:category_t 中的方法列表是插入到主類的方法列表前面(類似利用鏈表中的 next 指針來進行插入),所以這里 Category 中實現(xiàn)的方法并不會真正的覆蓋掉主類中的方法,只是將 Category 的方法插到方法列表的前面去了。運行時在查找方法的時候是順著方法列表的順序查找的,它只要一找到對應名字的方法,就會停止查找,這里就會出現(xiàn)覆蓋方法的這種假象了。
// 這里大概就類似這樣子插入newproperties->next = cls->data()->properties;cls->data()->properties = newproperties;,
通過上面的簡述,我們大概了解了 Category 的實現(xiàn)原理,就可以知道 Extension 跟 Category 是兩種實現(xiàn)模式,一個是在編譯時期實現(xiàn)的,一個是在運行時期決定的。
Category 為什么不能添加實例變量
通過結構體 category_t ,我們就可以知道,在 Category 中我們可以增加實例方法、類方法、協(xié)議、屬性。這里沒有 objc_ivar_list 結構體,代表我們不可以在分類中添加實例變量。
因為在運行期,對象的內(nèi)存布局已經(jīng)確定,如果添加實例變量就會破壞類的內(nèi)部布局,這個就是 Category 中不能添加實例變量的根本原因。
項目中用 Category 一般用來實現(xiàn)什么功能
通過分類來為已知的類擴展方法和屬性,Category 不會為我們的屬性添加實例變量和存取方法,我們可以通過關聯(lián)對象這個技術來實現(xiàn)對象綁定
通過實現(xiàn)分類的 load 方法來實現(xiàn) Method Swizzling
將一個類拆分成多個實現(xiàn)文件,典型的就是將項目中 AppDelegate 拆分。 AppDelegate 作為程序的入口,一般都會實現(xiàn)各種第三方 SDK 的初始化、寫各種版本的容錯代碼、實現(xiàn)通知、支付邏輯等等功能,所以 AppDelegate 這個類很容易臃腫,這個時候可以通過實現(xiàn) AppDelegate 分類來將不同的業(yè)務代碼分離。
被 Category “覆蓋” 的方法是有辦法調用到的(但是項目中暫時還沒遇到這種場景 - -/...)
關聯(lián)對象存儲原理
有人可能會有疑問,關聯(lián)對象底層是怎么實現(xiàn)的呢,它是不是通過屬性生成了成員變量,然后合并到了類對象的成員屬性列表中去呢?其實不是的,關聯(lián)對象是另外單獨存儲的,底層實現(xiàn)關聯(lián)對象技術的核心對象有4個:
ObjcAssociation:這個對象里面有2個成員uintptr_t _policy和id _value,這兩個很顯然就是我們設置關聯(lián)對象傳入的參數(shù)policy和value。
ObjectAssociationMap:這是一個HashMap(以鍵值對方式存儲,可以理解為是一個字典),以設置關聯(lián)對象時傳入的key值作為HashMap的鍵,以ObjcAssociation對象作為HashMap的值。比如一個分類添加了3個屬性,那一個實例對象給這3個屬性都賦值了,那么這個HashMap中就有3個元素,如果給這個實例對象的其中一個屬性賦值為nil,那這個HashMap就會把這個屬性對應的鍵值對給移除,然后HashMap中就還剩2個元素。
AssociationsHashMap:這也是一個HashMap,以設置關聯(lián)屬性時傳入的參數(shù)object作為鍵(實際是對object對象通過某個算法計算出一個值作為鍵)。以ObjectAssociationMap作為值。所以當某個類(前提是這個類的分類中有設置關聯(lián)對象)每實例化一個對象,這個HashMap就會新增一個元素,當某個實例化對象被釋放時,其對應的鍵值對也會被這個HashMap給移除。注意整個程序運行期間,AssociationsHashMap只會有一個,也就是說所有的類的關聯(lián)對象信息都是存儲在這個HashMap中。
AssociationsManager:從名字就可以看出它是一個管理者,注意整個程序運行期間它也只有一個,他就只包含一個AssociationsHashMap。
總結
我們這是簡單描述 Category 的實現(xiàn)原理以及項目中 Category 的一些運用,Category 的知識點遠不止這些,對于 Category 具體實現(xiàn)代碼可以閱讀參考文獻中的鏈接,看完可以對 Category 有更高層次的理解。
對于這道面試題,我們還可以擴展其它問題:關聯(lián)對象是如何實現(xiàn)的,Category 中可以實現(xiàn) load 方法嗎等問題。這些問題我們在后面都會涉及。