第一部分: OC語法

前言:

我們直接用面試題的方式來回答和解讀oc語法的底層原理。看下面的面試題:

有關(guān)oc面相對(duì)象的:

1,面向?qū)ο缶幊蘋OP是什么意思?談?wù)勀銓?duì)OOP的理解。
2,一個(gè)nsobject占用多少內(nèi)存空間?
3,對(duì)象的isa指針和superclass指向哪里?
4,oc對(duì)象的類信息存放在哪?
5,oc對(duì)象的本質(zhì)是什么?
6,談?wù)勀銓?duì)isa指針的了解?

KVO有關(guān)的:

1,oc是如何實(shí)現(xiàn)對(duì)一個(gè)對(duì)象的KVO?
2,如何手動(dòng)觸發(fā)KVO?
3,直接修改成員變量會(huì)觸發(fā)KVO嗎?

KVC相關(guān)的:

1,KVC的實(shí)現(xiàn)原理是怎樣的?
2,通過KVC修改屬性會(huì)觸發(fā)KVO嗎?

Category分類相關(guān)的:

1,Caregory是什么?什么場合使用?
2,Caregory的實(shí)現(xiàn)原理?
3,Catefory和Class Extension的區(qū)別是什么?
4,Category中有l(wèi)oad方法嗎?load方法和intialize方法的區(qū)別是什么?

關(guān)聯(lián)對(duì)象相關(guān)的 :

1,關(guān)聯(lián)對(duì)象常用的api有哪些?
2,關(guān)聯(lián)對(duì)象的底層是怎么實(shí)現(xiàn)的?

Block相關(guān)(寫了另一篇來講了)

鏈接:http://www.lxweimin.com/p/eed13c33c8bc

答案和詳解

一, OC對(duì)象的本質(zhì)

面試題1:面向?qū)ο缶幊蘋OP是什么意思?談?wù)勀銓?duì)OOP的理解。
面試題2: 一個(gè)nsobject對(duì)象占用多少內(nèi)存空間?

答: 系統(tǒng)給nsobject分配了16個(gè)字節(jié)的空間(可以通過#import<malloc/malloc.h>框架下的malloc_size()方法來獲取);
但實(shí)際上在64bit環(huán)境下nsobject對(duì)象內(nèi)部只使用了8字節(jié)的空間(可以通過#import<objc/runtime.h>的class_getInstanceSize()方法來獲取).
以上是一個(gè)沒有屬性和成員變量的對(duì)象所占用的字節(jié), 通過上面兩個(gè)方法, 我們還可以發(fā)現(xiàn), 對(duì)于擁有多個(gè)成員變量和屬性的對(duì)象, 系統(tǒng)給其分配的內(nèi)存空間是以16的倍數(shù)增長的, 而實(shí)際內(nèi)部占用的空間則不一定是16的倍數(shù).

面試題3: oc對(duì)象的分類有幾種? isa指針是怎樣的?

答: 三種, 分別是:
instance對(duì)象(實(shí)例對(duì)象);
class對(duì)象(類對(duì)象);
meta-class對(duì)象(元類對(duì)象).
下面是他們的isa指針指向和superclass指針的指向。其中需要注意的是:基類元對(duì)象的isa指針指向自己;基類元對(duì)象的superclass指針指向基類的class對(duì)象,而基類對(duì)象的superclass指針指向nil。


isa指向.png
面試題4:oc對(duì)象的本質(zhì)是什么?

OC對(duì)象的本質(zhì)是一個(gè)結(jié)構(gòu)體。通過將OC的文件編譯成cpp文件之后可以看到,一個(gè)OC的類編譯之后就是一個(gè)cpp的結(jié)構(gòu)體,結(jié)構(gòu)是這樣的:


類的本質(zhì).png

對(duì)于自定義的類,例如GQStudent類,編譯后的結(jié)構(gòu)體跟上面是一樣的,只是編譯后的結(jié)構(gòu)體的名稱一般都是student_IMPL{}.

面試題5:oc對(duì)象的類信息存放在哪?

根據(jù)上面的兩個(gè)題,那么這個(gè)問題就很好回答了。類對(duì)象的信息是存放在objc_class結(jié)構(gòu)體的bits結(jié)構(gòu)體中;并且通過FAST_DATA_MASK掩碼可以得到bits結(jié)構(gòu)體地址,在bits中的ro結(jié)構(gòu)體里面存放著oc對(duì)象的類信息。ro包括的信息有:instanceSize表示對(duì)象占用內(nèi)存空間大小,name類名,ivars成員變量列表等等。

面試題6:談?wù)勀銓?duì)isa指針的了解?

在64bits之前,isa指針指示單純的指針,里面存放著所指向的對(duì)象地址。在64bits之后,isa指針被優(yōu)化成tagged pointer,里面不僅能存放地址,也能存放相對(duì)較小的number string等對(duì)象,而不需要為這樣的對(duì)象開辟新的內(nèi)存地址,用來減小內(nèi)存開銷和調(diào)用效率。并且在64bit之后,需要通過ISA_MASK掩碼才能獲得真正的isa地址

二, KVO有關(guān)的

面試題1: oc是如何實(shí)現(xiàn)對(duì)一個(gè)對(duì)象的KVO?

當(dāng)我們對(duì)一個(gè)instance對(duì)象進(jìn)行KVO的時(shí)候,runtime的API會(huì)動(dòng)態(tài)生成一個(gè)全新的中間對(duì)象,當(dāng)修改instance中的屬性時(shí),會(huì)調(diào)用Fundation的NSSet**ValueAndNotify{
[self willChangeValueForkey:@“”];
[原來的instance的setter方法實(shí)現(xiàn)];
[self didChageValueForKey:@“”];
},并且會(huì)觸發(fā)內(nèi)部的observe監(jiān)聽器的監(jiān)聽方法(observeValueForKeyPath:ofObject:change:context:)以實(shí)現(xiàn)對(duì)對(duì)象的監(jiān)聽。
以MJPerson為例,如圖所示:
原來的MJPerson的instance對(duì)象和class對(duì)象之間新建一個(gè)中間對(duì)象NSKVONotifying_MJPerson對(duì)象,并且把instance的isa指向NSKVONotifying_MJPerson,把NSKVONotifying_MJPerson的superclass指針指向原來的class對(duì)象。NSKVONotifying_MJPerson中對(duì)setter方法進(jìn)行了重寫,重寫的原理是在原來的setter方法賦值之前調(diào)用[self willChangeValueForKey:@""]和賦值之后調(diào)用[self didChangeValueForKey:@""]. 并且重寫后的方法會(huì)觸發(fā)NSFundation_NSSet
ValueAndNotify方法發(fā)出通知。所以這是實(shí)現(xiàn)的原理。

KVO原理.png

面試題 2:如何手動(dòng)觸發(fā)KVO?

手動(dòng)的調(diào)用willChangeValueForKey:或者didChangeValueForKey:方法都能觸發(fā)KVO。

面試題 3:直接接修改成員變量會(huì)觸發(fā)KVO嗎?

不會(huì),因?yàn)椴唤?jīng)過上面1說的那個(gè)過程。

三, KVC相關(guān)的:

面試題 1:KVC的實(shí)現(xiàn)原理是怎樣的?

KVC全程是Key_Value Coding也就是俗稱的“鍵值編碼”,可以通過key來訪問某個(gè)屬性。常見的KVC的API接口有:


kvc接口.png

當(dāng)我們調(diào)用setValue:forKey:方法時(shí),會(huì)先按照次序調(diào)用setKey:和_setKey:方法修改屬性值,而如果調(diào)用不成功則會(huì)調(diào)用accessInstanceVariableDirectly:方法,如果返回YES則按照次序調(diào)用_key:和 _isKey: 和 key:和 _isKey:方法獲取屬性并直接賦值;而如果accessInstanceVariableDirectly:返回YES后調(diào)用了前面四個(gè)方法之后沒找到屬性值,或者accessInstanceVariableDirectly返回了NO,則調(diào)用setValue:forUndefineKey:方法并拋出異常NSunknownKeyException。下圖是調(diào)用的過程圖:


kvc賦值.png

當(dāng)我們調(diào)用valueForKey:方法時(shí)候,過程也是類似的。會(huì)首先依次調(diào)用getKey:→key:→isKey:→_key方法,找到了則直接調(diào)用獲取屬性;找不到則會(huì)調(diào)用accessInstanceVariablesDirectly如果返回了YES,則依次調(diào)用_key:→ _isKey:→ key:→ isKey:方法找到了則直接調(diào)用取值。如果上面找不到或者accessInstanceVariablesDirectly直接返回NO,則調(diào)用setValue:forUndefineKey:方法并拋出異常NSunknownKeyException。下圖是調(diào)用的過程圖:
kvc取值.png
面試題 2:通過KVC修改屬性會(huì)觸發(fā)KVO嗎?

會(huì)。因?yàn)镵VO調(diào)用過程中會(huì)調(diào)用setter方法,進(jìn)而觸發(fā)KVO。

四,Category(也叫分類)相關(guān)的:

面試題 1:Caregory是什么?什么場合使用?

category也叫分類,是runtime支持實(shí)現(xiàn)的對(duì)類進(jìn)行動(dòng)態(tài)添加拓展, 是oc語法的一種。
分類使用的場合主要有:
1,降低單個(gè)類的體積,降低耦合性,可以多人開發(fā);
2,可以對(duì)系統(tǒng)的類進(jìn)行拓展;
3,可以模擬多繼承;
4,可以用來對(duì)靜態(tài)庫方法進(jìn)行公開。

面試題 2:Caregory的實(shí)現(xiàn)原理?

category在編譯后是一個(gè)結(jié)構(gòu)體category_t,里面存放著分類的類方法、方法、協(xié)議信息等,但是這個(gè)結(jié)構(gòu)體里并沒有屬性列表,所以不能往分類里添加屬性。在程序運(yùn)行時(shí)候,runtime會(huì)將一個(gè)類的分類動(dòng)態(tài)的合并到類信息里面(包括類對(duì)象和元類對(duì)象)。并且后編譯的category會(huì)排在class的methods列表前面,所以當(dāng)一個(gè)類有多個(gè)category時(shí),后面編譯的category的類方法和方法等會(huì)優(yōu)先被調(diào)用。但這不是覆蓋,而是優(yōu)先調(diào)用。

面試題 3:Catefory和Class Extension的區(qū)別是什么?

category和class extension是兩個(gè)不同的概念,都是oc語法中的一種,只是中文叫法有點(diǎn)類似。
它們的區(qū)別主要有:
1,category一般只能用來對(duì)類進(jìn)行方法的擴(kuò)展,而不能添加成員變量屬性變量;而class extension可以添加屬性和方法并且會(huì)自動(dòng)生成setter和getter方法。
2,category中的方法是運(yùn)行時(shí)合并到原來的類信息之中,而class extension中的屬性和方法等是編譯時(shí)候就添加到原來的類信息之中了。所以當(dāng)category中聲明的方法沒有實(shí)現(xiàn)時(shí)候,編譯時(shí)候也不會(huì)被提醒,而class extension就會(huì)報(bào)警。
3,class extension沒有獨(dú)立的.m文件,是需要在原來的類的.m文件中進(jìn)行實(shí)現(xiàn)的,也就是需要有原來的類代碼,需要依托源代碼實(shí)現(xiàn)。而category有獨(dú)立的.m文件,可以在.m中實(shí)現(xiàn)方法。

面試題 4:Category中有l(wèi)oad方法嗎?load方法什么時(shí)候調(diào)用?load方法可以被繼承嗎?load方法和intialize方法的區(qū)別是什么?

category中是有l(wèi)oad方法的。+load方法是在runtime加載類和分類的時(shí)候調(diào)用,并且在整個(gè)程序運(yùn)行過程中知知調(diào)用一次。load方法可以被繼承但實(shí)際開發(fā)中我們都最好不要主動(dòng)調(diào)用laod方法,讓系統(tǒng)自動(dòng)調(diào)用。而且調(diào)用的次序分別是:
先調(diào)用類的+load方法(其中,是按照編譯順序來調(diào)用的,先編譯先調(diào)用,而且調(diào)用子類的load方法之前會(huì)先調(diào)用父類的load);
然后再調(diào)用分類的laod(其中,分類是按照編譯順序來調(diào)用的,先編譯的會(huì)先被調(diào)用)。
而且由上面的順序可以看出,load方法是通過方法地址直接調(diào)用的,而不是通過objc_msgSend方法來調(diào)用。

intialize方法是在類第一次接收到信息時(shí)候調(diào)用。并且是先調(diào)用父類的intialize方法再調(diào)用子類的intialize方法(其實(shí)就是:先初始化父類在初始化子類,并且每個(gè)類只會(huì)初始化一次)。
intialize方法跟load方法最大的區(qū)別是調(diào)用不同,intialize方法是通過消息objc_msgSend方法調(diào)用的,而load方法是通過內(nèi)存地址直接調(diào)用的。所以他們有以下各自的特點(diǎn):
當(dāng)子類的intialize方法沒有實(shí)現(xiàn)的時(shí)候,就會(huì)調(diào)用父類的intialize方法(所以父類的intialize方法有可能會(huì)被多次調(diào)用);
如果分類中實(shí)現(xiàn)了intialize方法,則會(huì)覆蓋父類的intialize方法的調(diào)用。

面試題 5:如何給Category添加屬性?

category的底層結(jié)構(gòu)限制了無法給分類添加屬性。但是可以通過關(guān)聯(lián)對(duì)象來間接地實(shí)現(xiàn)給分類添加屬性。

五,關(guān)聯(lián)對(duì)象(associatedObject)相關(guān)的:

面試題 1:關(guān)聯(lián)對(duì)象常用的api有哪些?

常用的有三個(gè)接口:


image.png

其中,對(duì)于key,除了可以使用屬性名來作為key以外,還可以是使用方法簽名來作為key這樣可以較好的保證其唯一性。例如可以像下面這樣設(shè)置key:


image.png

然后,關(guān)于objc_AssociationPolicy可以參考下面的表格:
image.png
面試題 1:關(guān)聯(lián)對(duì)象的實(shí)現(xiàn)原理?

關(guān)聯(lián)對(duì)象技術(shù)的核心對(duì)象有以下四個(gè):
AssosiationManager;
AssosiationHashMap;
ObjectAssosiationMap;
ObjectAssosiation.
他們的之間的關(guān)系是這樣的:


image.png

原理是這樣的:關(guān)聯(lián)對(duì)象并不是存儲(chǔ)在被關(guān)聯(lián)對(duì)象本身的內(nèi)存中,而是存儲(chǔ)在一個(gè)由runtime來保持的全局的統(tǒng)一的AssosiationManager對(duì)象中;這個(gè)對(duì)象持有一個(gè)AssosiationHashMap,在AssosiationHashMap里面存儲(chǔ)著被關(guān)聯(lián)對(duì)象的key以及被關(guān)聯(lián)的屬性O(shè)bjectAssosiationMap,而ObjectAssosiationMap中就存儲(chǔ)著要關(guān)聯(lián)的屬性。
所以,當(dāng)我們往一個(gè)對(duì)象里添加關(guān)聯(lián)對(duì)象的時(shí)候,AssosiationManager就會(huì)往自己的AssosiationHashMap中添加一個(gè)鍵值對(duì),這個(gè)鍵值對(duì)里就存著需要添加的屬性信息。當(dāng)我們要移除關(guān)聯(lián)對(duì)象時(shí)候,除了可以調(diào)用移除方法以外,還可以通過設(shè)置nil來移除。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,030評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,310評(píng)論 3 415
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,951評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,796評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,566評(píng)論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,055評(píng)論 1 322
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,142評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,303評(píng)論 0 288
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,799評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,683評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,899評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,409評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,135評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,520評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,757評(píng)論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,528評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,844評(píng)論 2 372

推薦閱讀更多精彩內(nèi)容