學(xué)而時習(xí)之,不亦說乎
前面的話
原諒我池充,我是個標(biāo)題黨,所有文章的名字只是我的噱頭缎讼,偉大的喬布斯告訴我們"Stay hungry,Stay foolish"收夸,希望大家有空杯心態(tài) ,一起學(xué)習(xí)血崭,一起進(jìn)步卧惜。
分類Category 我想絕大部分人應(yīng)該不陌生厘灼,就算自己沒寫過分類Category ,一些知名的三方庫里都會用到咽瓷。 而且這也是各一線大廠面試出現(xiàn)頻率很高的題 设凹。
上一篇文章,有人反饋說茅姜,文章提到的問題闪朱,是最基礎(chǔ)的。意思是還要深挖钻洒?更多深底層奋姿? 我知道人的精力是有限的,每個人準(zhǔn)備面試時間也是有限的素标,在有限的時間里胀蛮,最大限度的提高復(fù)習(xí)效率,這才是正道糯钙。 我也盡量在文中體現(xiàn)適當(dāng)?shù)脑矸嗬牵创a細(xì)節(jié)。
不禁感慨想起一句話: 面試造火箭任岸,入職擰螺絲再榄。 不過小白們也不要憤憤不平。 畢竟好崗位競爭激烈享潜,只有面試造火箭才能找出出更優(yōu)秀人困鸥。不然大家都考100分,那怎么區(qū)分出誰更厲害剑按。
開始面試
我正在會議室略有緊張的等待面試疾就,忽然看到一個穿著格子襯衫,大腹便便的中年男子拿著簡歷向我走來艺蝴, 我看著他頭上快要絕頂?shù)念^發(fā)猬腰,心想這肯定是個iOS開發(fā)技術(shù)牛逼閃閃的老前輩。
還好看過《iOS之面試總結(jié)》系列猜敢,想想現(xiàn)在是滿腹經(jīng)綸姑荷,剛緊張到提到嗓子眼的心,又按下去了缩擂,淡定從容鼠冕,一點(diǎn)都不虛好伐,就是這么自信淡定胯盯。
我什么時候也能變成那樣厲害的高手
小伙子懈费,你家里里提到用過Category,那你可以說他的使用場景是什么博脑,用途是什么憎乙?
帥氣逼人的面試官您好薄坏,Category的用戶可以歸納為以下幾點(diǎn):
- 給現(xiàn)有類添加方法,豐富現(xiàn)有類的功能寨闹。 比如有的人就為NSString這類添加了一些很實用的方法(判斷字符串是否郵箱胶坠,轉(zhuǎn)化字符串為MD5)
- 分解代碼龐大功能復(fù)雜的類。把功能復(fù)雜代碼很多的類繁堡,可以按照不同功能的做分類沈善,同一功能放到一個文件里,體現(xiàn)單一職責(zé)原則椭蹄。
- 聲明私有方法闻牡。比如定義一個分類,只有頭文件放到對應(yīng)宿主.m里绳矩,滿足私有方法的聲明和使用罩润,不暴露具體實現(xiàn)。
還有其他用法翼馆, 但是蘋果不歡迎這樣用法: 把系統(tǒng)Framework的私有方法公開化割以。
那你說說Category的底層實現(xiàn)是什么?
熟悉oc底層的同學(xué)知道应媚,在runtime層都是類和對象都是 struct表示的严沥,category也不例外,category用結(jié)構(gòu)體category_t中姜,結(jié)構(gòu)體包含:
- 類的名字(name)
- 類(cls)
- category中所有給類添加的實例方法的列表(instanceMethods)
- category中所有添加的類方法的列表(classMethods)
- category實現(xiàn)的所有協(xié)議的列表(protocols)
- category中添加的所有屬性(instanceProperties)
typedef struct category_t {
const char *name;
classref_t cls;
struct method_list_t *instanceMethods;
struct method_list_t *classMethods;
struct protocol_list_t *protocols;
struct property_list_t *instanceProperties;
} category_t;
從category的定義也可以看出category的可為(可以添加實例方法消玄,類方法,甚至可以實現(xiàn)協(xié)議丢胚,添加屬性)翩瓜。
category成員變量列表是只讀,所以category不能添加實例變量携龟。
小伙子不錯兔跌,那你再說說 Category的特點(diǎn)有什么?和 Class Extendsion(類擴(kuò)展)有什么區(qū)別骨宠?
帥氣的面試官浮定,這個不難。 我先說下特點(diǎn)层亿。
- 分類的特點(diǎn)
分類是運(yùn)行時決議。怎么理解運(yùn)行時決議呢? 編譯好的分類的文件立美,是沒把相應(yīng)分類的內(nèi)容加到宿主類上的匿又。只有在運(yùn)行時Runtime 才把分類的內(nèi)容添加到宿主類上。
- 類擴(kuò)展的用途是什么建蹄?
一般把不想對外公開一些類的方法碌更,屬性裕偿,成員變量的時候可以用類的擴(kuò)展
類擴(kuò)展代碼格式:
@interface XXX ()
//私有屬性
//私有方法(如果不實現(xiàn),編譯時會報警,Method definition for 'XXX' not found)
@end
- 分類和類擴(kuò)展的區(qū)別是痛单?
- 擴(kuò)展是編譯時決議嘿棘。 分類是運(yùn)行時決議
- 分類有聲明和實現(xiàn)。擴(kuò)展只以聲明的形式存在旭绒,多數(shù)情況下寄生于宿主類的.m中鸟妙。
- 系統(tǒng)類可以添加分類,但是不能為系統(tǒng)類添加擴(kuò)展挥吵。
那咱們再聊深一點(diǎn)的重父,Category中+load 和+initialize調(diào)用的順序是什么樣的? Category的+load 和+initialize 方法的區(qū)別什么忽匈?
- 調(diào)用順序:
- 類要優(yōu)先于分類調(diào)用+load方法房午。
- 先編譯的分類的+load方法會被優(yōu)先調(diào)。
- 父類+load優(yōu)先于子類丹允。
- 由于分類是objc_msgSend機(jī)制郭厌,+initialize在所有分類要優(yōu)先于類調(diào)用。 在類中 +initialize父類優(yōu)先于子類 雕蔽。如果子類沒有實現(xiàn)+initialize沪曙,會調(diào)用父類的+initialize(所以同一個父類的+initialize可能會被調(diào)用多次)。
是什么決定了哪個先編譯呢萎羔?如下圖Category1的+load比Category2的+load優(yōu)先調(diào)用液走。如果編譯文件順序換一下,被調(diào)用的順序也跟著變贾陷。
- +load 和+initialize 區(qū)別在于調(diào)用方式和調(diào)用時刻不同
調(diào)用方式不同:
+load是根據(jù)函數(shù)地址直接調(diào)用缘眶,initialize是通過objc_msgSend調(diào)用調(diào)用時刻不同:
+load方法會在runtime加載類、分類時調(diào)用(而且程序運(yùn)行過程中只調(diào)用一次)髓废。
+initialize是類第一次接收到消息的時候調(diào)用(即是類調(diào)用alloc時)巷懈,每一個類只會initialize一次(上面提到子類沒有實現(xiàn)+initialize,會調(diào)用父類的+initialize慌洪。這樣父類的initialize方法可能會被調(diào)用多次)
那怎么理解category方法覆蓋的問題顶燕?
分類添加的方法可以“覆蓋”原類方法
其實覆蓋沒有真正的覆蓋。如果在category里添加了methodA冈爹,那么原類的methodA也是存在的涌攻,沒有真正覆蓋掉。只是系統(tǒng)只會調(diào)用后來category添加的 methodA频伤。
因為category的methodA被放到了方法列表的前面恳谎,原來類中methodA被放到了方法列表的后面,在運(yùn)行時查找方法會先找到了category 添加的同名方法,就產(chǎn)生了“覆蓋“原來類的同名方法的效果因痛。
如果多個分類有同名方法婚苹,那么誰能生效取決于誰最后參與編譯。最后參與編譯的分類對應(yīng)的方法就會生效鸵膏。
能否給category添加實例變量膊升?那如何給分類添加實例變量?
不能谭企。
從上面提到的分類的底層實現(xiàn)廓译,category成員變量列表是只讀,所以category不能添加實例變量赞咙。
那怎么添加呢责循? 直接添加肯定是不行,可以間接的添加.
思路1
給分類添加全局變量攀操,并且手動重寫setter/getter方法實現(xiàn)院仿。但是全局變量有很多隱患,對象銷毀時無法銷毀速和。不建議采用歹垫。思路2
用關(guān)聯(lián)對象方法添加實例變量。
通過runtime提供的關(guān)聯(lián)對象的方法可以簡潔的給分類添加成員變量颠放。
通過下面提供的關(guān)聯(lián)對象下API排惨,可以實現(xiàn)給分類添加實例變量:
添加關(guān)聯(lián)對象
void objc_setAssociatedObject(id object, const void * key, id value, objc_AssociationPolicy policy)
獲得關(guān)聯(lián)對象
id objc_getAssociatedObject(id object, const void * key)
移除所有的關(guān)聯(lián)對象
void objc_removeAssociatedObjects(id object)
簡單說下關(guān)聯(lián)對象方法添加實例變量原理:
其實關(guān)聯(lián)對象并沒有添加到實例變量到被關(guān)聯(lián)對象內(nèi)存中,
關(guān)聯(lián)對象有一個全局內(nèi)容管理器(AssociationsManager)碰凶,關(guān)聯(lián)對象通過上面提到的三個API暮芭,操作實例變量 存取,移除欲低。從而完成了對分類的添加實例變量辕宏。
面試結(jié)束
小伙子回答的不錯,很對我口味砾莱,記得明天再來瑞筐,明天還有更精彩的面試其他內(nèi)容。 我的內(nèi)心OS:天吶嚕腊瑟,明天還有>奂佟! 這是要在走禿(走向禿頂?shù)牡缆? 越走越遠(yuǎn)啊闰非。 (為了下一篇文章膘格,強(qiáng)行做引子,哈哈)
堅持看到這里的同學(xué)河胎,你們個個都是人才闯袒,我好喜歡。