iOS-Runtime-原理篇

Runtime

號(hào)外 : 一些關(guān)于runtime的小demo在我的下一篇文章iOS-Runtime-實(shí)踐篇

我們都知道Objective-C是一門(mén)動(dòng)態(tài)語(yǔ)言, 動(dòng)態(tài)之處體現(xiàn)在它將許多靜態(tài)語(yǔ)言編譯鏈接時(shí)要做的事通通放到運(yùn)行時(shí)去做, 這大大增加了我們編程的靈活性.

毫不過(guò)分地說(shuō), Runtime就是OC的靈魂.

接下來(lái)我就要撥開(kāi)OC最外層的外衣, 帶大家看看OC的真面目(C/C++).

目錄

  1. 類和對(duì)象
  2. 消息發(fā)送和轉(zhuǎn)發(fā)
  3. KVO原理

類和對(duì)象

@interface Person : NSObject {
    NSString *_name;
    int _age;
}

- (void)study;
+ (void)study;

@end

@implementation Person

- (void)study
{
    NSLog(@"instance - study");
}

+ (void)study
{
    NSLog(@"class - study");
}

@end

為了更好地說(shuō)明類在底層的表現(xiàn)形式是怎樣, 我們將上面代碼利用clang -rewrite-objc Person.m指令將其用C/C++重寫(xiě), 一窺究竟.

把不必要的刪除, 整理后為下面

struct _class_t { 
    struct _class_t *isa; // isa指針
    struct _class_t *superclass; // 父類
    void *cache;
    void *vtable;
    struct _class_ro_t *ro; // class的其他信息
};
// class包含的信息
struct _class_ro_t { 
    unsigned int flags;
    unsigned int instanceStart;
    unsigned int instanceSize;
    unsigned int reserved;
    const unsigned char *ivarLayout;
    const char *name; // 類名
    const struct _method_list_t *baseMethods; // 方法列表
    const struct _objc_protocol_list *baseProtocols; // 協(xié)議列表
    const struct _ivar_list_t *ivars; // ivar列表
    const unsigned char *weakIvarLayout;
    const struct _prop_list_t *properties; // 屬性列表
};

// Person(class)
struct _class_t OBJC_CLASS_$_Person  = {
    .isa = &OBJC_METACLASS_$_Person, // 指向Person-metaclass
    .superclass = &OBJC_CLASS_$_NSObject, // 指向NSObject-class
    .cache = &_objc_empty_cache,
    0, // unused, was (void *)&_objc_empty_vtable,
    &_OBJC_CLASS_RO_$_Person, // 包含了實(shí)例方法, ivar信息等
};

// Person(metaclass)
struct _class_t OBJC_METACLASS_$_Person = {
    .isa = &OBJC_METACLASS_$_NSObject, // 指向NSObject-metaclass
    .superclass = &OBJC_METACLASS_$_NSObject, // 指向NSObject-metaclass
    .cache = &_objc_empty_cache,
    0, // unused, was (void *)&_objc_empty_vtable,
    &_OBJC_METACLASS_RO_$_Person, // 包含了類方法
};

原來(lái)(顯然), 我們的類其實(shí)就是一個(gè)結(jié)構(gòu)體!!! 類跟我們的對(duì)象一樣, 都有一個(gè)isa指針, 所以類其實(shí)也是對(duì)象的一種.

isa指針

isa指針?lè)浅V匾? 對(duì)象需要通過(guò)isa指針找到它的類, 類需要通過(guò)isa找到它的元類. 這在調(diào)用實(shí)例方法和類方法的時(shí)候起到重要的作用.


isa指針

實(shí)例對(duì)象在調(diào)用方法時(shí), 首先通過(guò)isa指針找到它所屬的類, 然后在類的緩存(cache)里找該方法的IMP, 如果沒(méi)有, 則去類的方法列表中查找, 然后找到則調(diào)用該方法, 找不到則報(bào)錯(cuò).

類對(duì)象調(diào)用方法則如出一轍, 通過(guò)isa指針找到元類, 然后就跟上述一致了. 這里涉及的發(fā)送消息機(jī)制下面會(huì)詳細(xì)講..

下面展示一些運(yùn)行時(shí)動(dòng)態(tài)獲取對(duì)象和類的屬性的C語(yǔ)言方法

類和類名 :

// 返回對(duì)象的類
Class object_getClass ( id obj );
// 設(shè)置對(duì)象的類
Class object_setClass ( id obj, Class cls );
// 獲取類的父類
Class class_getSuperclass ( Class cls );
// 創(chuàng)建一個(gè)新類和元類
Class objc_allocateClassPair ( Class superclass, const char *name, size_t extraBytes );
// 在應(yīng)用中注冊(cè)由objc_allocateClassPair創(chuàng)建的類
void objc_registerClassPair ( Class cls );
// 銷(xiāo)毀一個(gè)類及其相關(guān)聯(lián)的類
void objc_disposeClassPair ( Class cls );
// 獲取類的類名
const char * class_getName ( Class cls );
// 返回給定對(duì)象的類名
const char * object_getClassName ( id obj );

ivar和屬性 :

// 添加成員變量
BOOL class_addIvar ( Class cls, const char *name, size_t size, uint8_t alignment, const char *types );
// 添加屬性
BOOL class_addProperty ( Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount );
// 返回類的某一ivar
Ivar class_getInstanceVariable(__unsafe_unretained Class cls, const char *name)
// 返回對(duì)象中實(shí)例變量的值
id object_getIvar ( id obj, Ivar ivar );
// 設(shè)置對(duì)象中實(shí)例變量的值
void object_setIvar ( id obj, Ivar ivar, id value );
// 獲取整個(gè)成員變量列表
Ivar * class_copyIvarList ( Class cls, unsigned int *outCount );
// 獲取屬性列表
objc_property_t * class_copyPropertyList ( Class cls, unsigned int *outCount );

方法 :

// 添加方法
BOOL class_addMethod ( Class cls, SEL name, IMP imp, const char *types );
// 獲取實(shí)例方法
Method class_getInstanceMethod ( Class cls, SEL name );
// 獲取類方法
Method class_getClassMethod ( Class cls, SEL name );
// 獲取所有方法的數(shù)組
Method * class_copyMethodList ( Class cls, unsigned int *outCount );
// 替代方法的實(shí)現(xiàn)
IMP class_replaceMethod ( Class cls, SEL name, IMP imp, const char *types );
// 交換兩個(gè)方法的實(shí)現(xiàn)(Method Swizzling)
void method_exchangeImplementations(Method m1, Method m2);

這里說(shuō)個(gè)注意點(diǎn) : addIvar并不能為一個(gè)已經(jīng)存在的類添加成員變量, 只能為那些運(yùn)行時(shí)動(dòng)態(tài)添加的類, 并且只能在objc_allocateClassPairobjc_registerClassPair這兩個(gè)方法之間才能添加Ivar.

消息發(fā)送和轉(zhuǎn)發(fā)機(jī)制

在OC中, 如果向某對(duì)象發(fā)送消息, 那就會(huì)使用動(dòng)態(tài)綁定機(jī)制來(lái)決定需要調(diào)用的方法. OC的方法在底層都是普通的C語(yǔ)言函數(shù), 所以對(duì)象收到消息后究竟要調(diào)用什么函數(shù)完全由運(yùn)行時(shí)決定, 甚至可以在運(yùn)行時(shí)改變執(zhí)行的方法.

[person read:book];
會(huì)被編譯成
objc_msgSend(person, @selector(read:), book);

objc_msgSend的具體流程如下

1. 通過(guò)isa指針找到所屬類
2. 查找類的cache列表, 如果沒(méi)有則下一步
3. 查找類的"方法列表"
4. 如果能找到與選擇子名稱相符的方法, 就跳至其實(shí)現(xiàn)代碼
5. 找不到, 就沿著繼承體系繼續(xù)向上查找
6. 如果能找到與選擇子名稱相符的方法, 就跳至其實(shí)現(xiàn)代碼
7. 找不到, 執(zhí)行"消息轉(zhuǎn)發(fā)".
方法查找

消息轉(zhuǎn)發(fā)

上面我們提到, 如果到最后都找不到, 就會(huì)來(lái)到消息轉(zhuǎn)發(fā)

  • 動(dòng)態(tài)方法解析 : 先問(wèn)接收者所屬的類, 你看能不能動(dòng)態(tài)添加個(gè)方法來(lái)處理這個(gè)"未知的選擇子"? 如果能, 則消息轉(zhuǎn)發(fā)結(jié)束.
  • 備胎(后備接收者) : 請(qǐng)接收者看看有沒(méi)有其他對(duì)象能處理這條消息? 如果有, 則把消息轉(zhuǎn)給那個(gè)對(duì)象, 消息轉(zhuǎn)發(fā)結(jié)束.
  • 消息簽名 : 這里會(huì)要求你返回一個(gè)消息簽名, 如果返回nil, 則消息轉(zhuǎn)發(fā)結(jié)束.
  • 完整的消息轉(zhuǎn)發(fā) : 備胎都搞不定了, 那就只能把該消息相關(guān)的所有細(xì)節(jié)都封裝到一個(gè)NSInvocation對(duì)象, 再問(wèn)接收者一次, 快想辦法把這個(gè)搞定了. 到了這個(gè)地步如果還無(wú)法處理, 消息轉(zhuǎn)發(fā)機(jī)制也無(wú)能為力了.
動(dòng)態(tài)方法解析 :

對(duì)象在收到無(wú)法解讀的消息后, 首先調(diào)用其所屬類的這個(gè)類方法 :

+ (BOOL)resolveInstanceMethod:(SEL)selector 
// selector : 那個(gè)未知的選擇子
// 返回YES則結(jié)束消息轉(zhuǎn)發(fā)
// 返回NO則進(jìn)入備胎

假如尚未實(shí)現(xiàn)的方法不是實(shí)例方法而是類方法, 則會(huì)調(diào)用另一個(gè)方法resolveClassMethod:

備胎 :

動(dòng)態(tài)方法解析失敗, 則調(diào)用這個(gè)方法

- (id)forwardingTargetForSelector:(SEL)selector
// selector : 那個(gè)未知的選擇子
// 返回一個(gè)能響應(yīng)該未知選擇子的備胎對(duì)象

通過(guò)備胎這個(gè)方法, 可以用"組合"來(lái)模擬出"多重繼承".

消息簽名 :

備胎搞不定, 這個(gè)方法就準(zhǔn)備要被包裝成一個(gè)NSInvocation對(duì)象, 在這里要先返回一個(gè)方法簽名

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
// NSMethodSignature : 該selector對(duì)應(yīng)的方法簽名
完整的消息轉(zhuǎn)發(fā) :

給接收者最后一次機(jī)會(huì)把這個(gè)方法處理了, 搞不定就直接程序崩潰!

- (void)forwardInvocation:(NSInvocation *)invocation
// invocation : 封裝了與那條尚未處理的消息相關(guān)的所有細(xì)節(jié)的對(duì)象

在這里能做的比較現(xiàn)實(shí)的事就是 : 在觸發(fā)消息前, 先以某種方式改變消息內(nèi)容, 比如追加另外一個(gè)參數(shù), 或是改變選擇子等等. 實(shí)現(xiàn)此方法時(shí), 如果發(fā)現(xiàn)某調(diào)用操作不應(yīng)該由本類處理, 可以調(diào)用超類的同名方法. 則繼承體系中的每個(gè)類都有機(jī)會(huì)處理該請(qǐng)求, 直到NSObject. 如果NSObject搞不定, 則還會(huì)調(diào)用doesNotRecognizeSelector:來(lái)拋出異常, 此時(shí)你就會(huì)在控制臺(tái)看到那熟悉的unrecognized selector sent to instance..

消息轉(zhuǎn)發(fā)流程

上面這4個(gè)方法均是模板方法氓润,開(kāi)發(fā)者可以override,由runtime來(lái)調(diào)用。最常見(jiàn)的實(shí)現(xiàn)消息轉(zhuǎn)發(fā)周偎,就是重寫(xiě)方法3和4埋涧,忽略這個(gè)消息或者代理給其他對(duì)象.

Method Swizzling

被稱為黑魔法的一個(gè)方法, 可以把兩個(gè)方法的實(shí)現(xiàn)互換.
如上文所述, 類的方法列表會(huì)把選擇子的名稱映射到相關(guān)的方法實(shí)現(xiàn)上, 使得"動(dòng)態(tài)消息派發(fā)系統(tǒng)"能夠據(jù)此找到應(yīng)該調(diào)用的方法. 這些方法均以函數(shù)指針的形式來(lái)表示, 這種指針叫做IMP,
<pre> id (*IMP)(id, SEL, ...)</pre>

NSString類的選擇子映射表

OC運(yùn)行時(shí)系統(tǒng)提供了幾個(gè)方法能夠用來(lái)操作這張表, 動(dòng)態(tài)增加, 刪除, 改變選擇子對(duì)應(yīng)的方法實(shí)現(xiàn), 甚至交換兩個(gè)選擇子所映射到的指針. 如,

經(jīng)過(guò)一些操作后的NSString選擇子映射表

如何交換兩個(gè)已經(jīng)寫(xiě)好的方法實(shí)現(xiàn)?

// 取得方法
Method class_getInstanceMethod(Class aClass, SEL aSelector)
// 交換實(shí)現(xiàn)
void method_exchangeImplementations(Method m1, Method m2)

通過(guò)Method Swizzling可以為一些完全不知道其具體實(shí)現(xiàn)的黑盒方法增加日志記錄功能, 利于我們調(diào)試程序. 并且我們可以將某些系統(tǒng)類的具體實(shí)現(xiàn)換成我們自己寫(xiě)的方法, 以達(dá)到某些目的. (例如, 修改主題, 修改字體等等)

KVO原理

KVO的實(shí)現(xiàn)也依賴Runtime. Apple文檔曾簡(jiǎn)單提到過(guò)KVO的實(shí)現(xiàn)原理 :

Automatic key-value observing is implemented using a technique called isa-swizzling... When an observer is registered for an attribute of an object the isa pointer of the observed object is modified, pointing to an intermediate class rather than at the true class ...

Apple的文檔提得不多, 但是大神Mike Ash在很早很早以前就已經(jīng)做過(guò)研究, 摘下了KVO神秘的面紗了, 有興趣的可以去查下, 這里不多深究, 只是簡(jiǎn)單闡述下原理.

原來(lái)當(dāng)你對(duì)一個(gè)對(duì)象進(jìn)行觀察時(shí), 系統(tǒng)會(huì)自動(dòng)新建一個(gè)類繼承自原類, 然后重寫(xiě)被觀察屬性的setter方法. 然后重寫(xiě)的setter方法會(huì)負(fù)責(zé)在調(diào)用原setter方法前后通知觀察者. 然后把原對(duì)象的isa指針指向這個(gè)新類, 我們知道, 對(duì)象是通過(guò)isa指針去查找自己是屬于哪個(gè)類, 并去所在類的方法列表中查找方法的, 所以這個(gè)時(shí)候這個(gè)對(duì)象就自然地變成了新類的實(shí)例對(duì)象.

不僅如此, Apple還重寫(xiě)了原類的- class方法, 視圖欺騙我們, 這個(gè)類沒(méi)有變, 還是原來(lái)的那個(gè)類. 只要我們懂得Runtime的原理, 這一切都只是掩耳盜鈴罷了.


后記

這只是我的Runtime文章的第一篇, 之后還會(huì)有Runtime實(shí)踐篇以及利用Runtime解決實(shí)際問(wèn)題的幾個(gè)demo, 感興趣的話還請(qǐng)大家關(guān)注關(guān)注_

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末淑趾,一起剝皮案震驚了整個(gè)濱河市僵刮,隨后出現(xiàn)的幾起案子递礼,更是在濱河造成了極大的恐慌涂臣,老刑警劉巖盾计,帶你破解...
    沈念sama閱讀 219,270評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件售担,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡署辉,警方通過(guò)查閱死者的電腦和手機(jī)族铆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)哭尝,“玉大人哥攘,你說(shuō)我怎么就攤上這事〔酿校” “怎么了逝淹?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,630評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)桶唐。 經(jīng)常有香客問(wèn)我栅葡,道長(zhǎng),這世上最難降的妖魔是什么莽红? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,906評(píng)論 1 295
  • 正文 為了忘掉前任妥畏,我火速辦了婚禮,結(jié)果婚禮上安吁,老公的妹妹穿的比我還像新娘醉蚁。我一直安慰自己,他們只是感情好鬼店,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,928評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布网棍。 她就那樣靜靜地躺著,像睡著了一般妇智。 火紅的嫁衣襯著肌膚如雪滥玷。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,718評(píng)論 1 305
  • 那天巍棱,我揣著相機(jī)與錄音惑畴,去河邊找鬼。 笑死航徙,一個(gè)胖子當(dāng)著我的面吹牛如贷,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播到踏,決...
    沈念sama閱讀 40,442評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼杠袱,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了窝稿?” 一聲冷哼從身側(cè)響起楣富,我...
    開(kāi)封第一講書(shū)人閱讀 39,345評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎伴榔,沒(méi)想到半個(gè)月后纹蝴,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體庄萎,經(jīng)...
    沈念sama閱讀 45,802評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,984評(píng)論 3 337
  • 正文 我和宋清朗相戀三年塘安,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了惨恭。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,117評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡耙旦,死狀恐怖脱羡,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情免都,我是刑警寧澤锉罐,帶...
    沈念sama閱讀 35,810評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站绕娘,受9級(jí)特大地震影響脓规,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜险领,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,462評(píng)論 3 331
  • 文/蒙蒙 一侨舆、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧绢陌,春花似錦挨下、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,011評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至秤掌,卻和暖如春愁铺,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背闻鉴。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,139評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工茵乱, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人孟岛。 一個(gè)月前我還...
    沈念sama閱讀 48,377評(píng)論 3 373
  • 正文 我出身青樓瓶竭,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親蚀苛。 傳聞我的和親對(duì)象是個(gè)殘疾皇子在验,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,060評(píng)論 2 355

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

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉玷氏,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,721評(píng)論 0 9
  • 一堵未、Runtime簡(jiǎn)介 Runtime簡(jiǎn)稱運(yùn)行時(shí)。OC就是運(yùn)行時(shí)機(jī)制盏触,也就是在運(yùn)行時(shí)候的一些機(jī)制渗蟹,其中最主要的是消...
    林安530閱讀 1,067評(píng)論 0 2
  • 參考鏈接: http://www.cnblogs.com/ioshe/p/5489086.html 簡(jiǎn)介 Runt...
    樂(lè)樂(lè)的簡(jiǎn)書(shū)閱讀 2,136評(píng)論 0 9
  • 如果想了解Runtime的實(shí)際應(yīng)用請(qǐng)看Runtime全面剖析之簡(jiǎn)單使用 一:Runtime簡(jiǎn)介二: Runtime...
    iYeso閱讀 810評(píng)論 0 2
  • 目錄 Objective-C Runtime到底是什么 Objective-C的元素認(rèn)知 Runtime詳解 應(yīng)用...
    Ryan___閱讀 1,939評(píng)論 1 3