一、數據結構
1.1、objc_object
我們平常用的實例對象都是id類型,對應到runtime 中的objc_object。
-
isa_t
共用體 -
關于isa操作相關
- 實例對象根據isa指針獲取類對象。
- 類對象根據isa指針獲取元類對象。
-
弱引用相關
- 標記一個對象是否曾經有弱引用指針。
-
關聯對象
- 設置關聯屬性的一些方法。
-
內存管理
- MRC下的retain、release等方法。
- ARC和MRC下用的autorelease方法。
1.2、objc_class
我們平常用的類,對應到runtime中的objc_class。
-
isa_t
因為繼承于objc_object,所以也有isa指針,用來指向類的元類對象。 -
Class superClass
父類對象 -
cache_t cache
方法緩存結構<消息傳遞會有涉及到> -
class_data_bits_t bits
bit通過&FAST_DATA_MASK獲取class_rw_t,class_rw_t包含方法、屬性、協議。
問題1:Class這個類是否是一個對象呢?
解釋:
是對象,因為Class對應runtime中的objc_class,objc_class繼承于objc_object,所以被稱為"類對象"。
1.3、isa指針
1.3.1、isa指針的概念
共用體isa_t
問題2:isa指針的含義
解釋:
- 包含指針型isa和非指針型isa。
- 指針型isa:isa的值代表Class地址。
- 非指針型isa:isa的值的部分代表Class地址。
-
應用場景:
- 32位架構用指針型的 64位架構用非指針型的。
- 這是一種提升內存利用的一種手段。
1.3.2、isa指向
-
關于對象,其指向類對象。
指向類對象 -
關于類對象,其指向元類對象。
指向元類對象
1.4、cache_t
1.4.1、概念
- 用于快速查找方法執行函數。
- 是可增量擴展的哈希表結構。
- 是局部性原理的最佳應用。
1.4.2、結構
cache_t是一個結構體。
可以理解為:
- 一個數組中有bucket_t。
- bucket_t是結構體。
- bucket_t包含兩個成員變量:key、IMP。
- key就是我們使用的selector。
- 根據key + 哈希算法,可以快速查找IMP。
1.5、class_data_bits_t
- class_data_bits_t主要是對class_rw_t的封裝。
- class_rw_t代表了類相關的讀寫信息、對class_ro_t的封裝。
- class_ro_t代表了類相關的只讀信息。
1.5.1、class_rw_t
- methods、properties、protocols是runtime動態添加的。
比如:分類中的方法會在運行時添加到methods中。 - methods、properties、protocols是可讀可寫的。
- methods、properties、protocols是二維數組。
- methods中存放有method_t對象。
1.5.2、class_ro_t
- name指的是類名
- ivars指的是成員變量
- methods、properties、protocols是編譯器內部生成的。
- methods、properties、protocols是只讀。
- methods、properties、protocols是一維數組。
- methods中存放有method_t對象。
1.5.3、method_t
- SEL name
方法名稱 - const char* types
返回值 + 參數 - IMP imp
無類型的函數指針,指向函數體
1.5.3.1、Type Encodings
- 不可變的字符指針
- 結構:返回值 + n個參數
實例分析:
-(void)aMethod;對應的Type Encodings
解析如下:
- aMethod的types === v@:。
- v代表返回值是void,無返回值。
- @代表對象id。
- :代表SEl(方法)
注意:
- 這里的第一個參數和第二個參數是固定的,并且是不可變的。
- 因為對于OC方法的調用或者說消息傳遞,到達runtime層面,都會轉換成objc_msgSend,它的前面兩個參數就是固定的objc_msgSend(obj, sel/@selector(aMethod)/);**。
1.6、整體數據結構
二、對象、類對象、元類對象
2.1、概念
- 類對象
存儲實例方法列表等信息。 - 元類對象
存儲類方法列表等信息。
2.2、isa指針指向圖
isa指針
- instance實例對象 --> Class類對象
- Class類對象 --> meta元類對象
- meta元類對象 --> 根meta元類對象
- 根meta元類對象 --> 根meta元類對象(自己)
superclass指針
- Rootclass類對象 --> nil
- Superclass類對象 --> Rootclass類對象
- Subclass類對象 --> Superclass類對象
- 根meta元類對象 --> Rootclass類對象
問題3:類對象、元類對象分別是什么,有什么區別和聯系?
- 概念
類對象是存儲實例方法列表等信息。
元類對象是存儲類方法列表等信息。 - isa指針指向圖
類對象的isa指針指向元類對象。
元類對象的isa指針指向根元類對象。
根元類對象的isa指針指向自己。
問題4:如果說調用的一個類方法,沒有對應的實現;但是,有同名的實例方法實現,會不會產生崩潰或者實際調用?
- 由于根meta元類對象的superclass指針指向的是根類對象,如果在根元類對象中沒有找到對應的類方法,就會去根類對象中去查找同名的實例方法。
問題5:筆試題
解釋:
- [self class]轉換為objc_msgSend(self, @selector(class))
- [super class]轉換為objc_msgSendSuper(super,@selector(class))
- [super class]雖然是super調用,但是super是一個結構體,里面包含一個id類型對象receiver,也就是當前對象。
- [self class]傳遞流程:在緩存中查找,沒有??在類對象中查找,沒有??在父類對象中查找,沒有??在NSObject中查找,有,返回信息。
- [super class]傳遞流程:直接在父類對象中查找,沒有??在NSObject中查找,有,返回信息。
三、消息傳遞
3.1、函數介紹
void objc_msgSend(void /* id self, SEL op, ... */ )
備注:
- 這里有2個固定參數,一個消息接受者(id類型),一個是方法選擇器名稱(SEL類型),其它才是后續方法傳遞的參數。
- 當前的接受者就是:當前對象。
void objc_msgSendSuper(void /* struct objc_super *super, SEL op, ... */ )
備注:
- 這里有2個固定參數,一個是結構體類型指針(objc_super),一個是方法選擇器名稱(SEL類型),其它才是后續方法傳遞的參數。
- 因為objc_super內部有一個receiver(id類型),就是當前對象;這個super就是編譯器關鍵字。
-
objc_msgSendSuper:是從父類對象的方法列表中開始查找;
objc_msgSend:是從當前對象緩存列表中查找。
3.2、消息傳遞流程
首先調用方法,查找緩存;如果有,就通過函數指針調用函數,完成方法調用。
這里的緩存指的是類對象的緩存,是通過哈希查找。如果緩存中沒有對應方法,就會根據當前實例的isa指針去查找類對象的方法列表;如果有,就通過函數指針調用函數,完成方法調用。
這里分二分查找和一般遍歷查找。如果當前類方法中沒有對應方法,就會逐級父類方法列表中去查找,是通過superclass指針;如果有,就通過函數指針調用函數,完成方法調用。
這里同樣先去緩存查找,再去方法列表查找。如果直到根類NSObject,都沒有查到對應方法,就會進入消息轉發流程。
四、消息傳遞三大步驟詳解
4.1、緩存查找
給定值是SEL(方法選擇器),目標值是對應的bucket_t中的IMP。
- 從緩存查找對應的實現, 是哈希查找。
- SEL(方法選擇器)就是key。
- 利用哈希算法,找到bucket_t,從而找到IMP。
- 哈希查找提高了查找效率。
4.2、當前類中查找
- 對于已排序好的列表,采用二分查找算法查找方法對應執行函數IMP。
- 對于沒有排序的列表,采用一般遍歷查找方法對應執行函數。
4.3、父類逐級查找
- 首先根據superclass指針查找有沒有父類;沒有父類,結束父類查找。
- 有父類,先去緩存查找;緩存查到了,就返回。
- 緩存沒有查到,就去方法列表查找;方法列表查到了,就返回。
- 如果上面兩步驟都沒有查到,就接著查下一個父類。
五、消息轉發
這里主要講的是實例方法的轉發流程。
5.1、(BOOL)resolveInstanceMethod:(SEL)sel
- 該方法是類方法。
- 參數是方法選擇器(SEL類型)。
- 返回值是BOOL類型。
- 如果返回值是YES,代表解決了當前實例方法的實現,就結束了消息轉發流程;如果返回值是NO,代表沒有解決,繼續消息轉發。
5.2、(id)forwardingTargetForSelector:(SEL)aSelector
- 該方法是實例方法。
- 參數是方法選擇器(SEL類型)。
- 返回值是一個對象,相當于指定的轉發目標(自己不處理,讓別人處理)。
- 如果指定了轉發目標,就結束了消息轉發流程;如果沒有指定轉發目標,繼續消息轉發。
5.3、(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
- 該方法是實例方法。
- 參數是方法選擇器(SEL類型)。
- 返回值是一個NSMethodSignature對象,是對一個方法選擇器的返回值類型,參數個數以及參數類型的封裝。
- 如果返回了方法簽名,則繼續調用forwardInvocation;否則就被標記為消息無法處理。
也就是常見的錯誤:unrecognized selector sent to instance 0x600000da1530。
5.4、(void)forwardInvocation:(NSInvocation *)anInvocation
- 該方法是實例方法。
- 參數是NSInvocation。
- 無返回值。
- 如果這里面能夠處理這條消息,消息轉發流程就結束了。
六、Method-Swizzling
主要代碼如下:
//獲取test方法
Method test = class_getInstanceMethod(self, @selector(test));
//獲取otherTest方法
Method otherTest = class_getInstanceMethod(self, @selector(otherTest));
//交換兩個方法的實現
method_exchangeImplementations(test, otherTest);
七、動態添加方法
class_addMethod(<#Class _Nullable __unsafe_unretained cls#>, <#SEL _Nonnull name#>, <#IMP _Nonnull imp#>, <#const char * _Nullable types#>)
- 第一個參數:為哪個類添加方法。
- 第二個參數:方法選擇器名稱(SEL類型)。
- 第三個參數:函數指針
- 第四個參數:Type Encodings。
八、動態方法解析
問題6、你是否使用過@dynamic關鍵字?
解釋:
也是借此考察運行時內容,因為使用@dynamic實際上是告訴編譯器,自己來實現setter與getter方法,不自動生成。
這樣聲明的屬性,即使你沒有實現setter與getter方法,編譯時,是不會報錯的;但是當你使用到setter與getter方法時(運行時),就會報錯。
問題7:編譯時語言與運行時語言的區別?
解釋:
- 動態運行時,語言將函數決議推遲到運行時。
- 編譯時,語言在編譯期進行函數決議。
問題8: [obj foo]和obj_msgSend()函數之間有什么關系?
解釋:
在經過編譯器轉換之后,就變成了obj_msgSend(obj,@selector(foo)),然后就開始了消息傳遞過程。
問題9:runtime如何通過Selector找到對應的IMP地址的?
解釋:
考察的是消息傳遞機制。
查找當前實例對應類對象的緩存??查找類對象的方法列表??逐級查找父級方法列表。
問題10:能否向編譯后的類增加實例變量?
解釋:
考察的是runtime數據結構。
因為編譯后的類,它已經完成了實例變量的布局,這個實例變量是存放在class_ro內部,這個是只讀的,所以不能向編譯后的類增加實例變量。
但是,可以向動態添加的類中添加實例變量。
問題11:函數調用與消息傳遞有怎樣的區別?
解釋:
考察消息傳遞機制
問題12:當我們調用一個方法沒有實現的時候,系統是如何為我們實現消息轉發過程的?
解釋:
考察消息傳遞機制