Runtime

runtime介紹:

runtime 叫運行時泽腮, 是一套底層C語言的API御蒲。我們平時編寫的OC代碼都是基于runtime實現(xiàn)的。

因為我們程序在編譯時期無法完成全部操作(方法的調用诊赊,類的創(chuàng)建)厚满,需要一個運行時的庫,所以出現(xiàn)了runtime碧磅。

OC在編譯時期不能決定真正調用那個函數(shù)碘箍,只有在運行的時候才會根據(jù)函數(shù)名找到對應的函數(shù)來調用

所以在編譯階段:OC可以調用任何函數(shù)遵馆,即使這個函數(shù)并未實現(xiàn),只要聲明過就不會報錯丰榴。

Runtime的作用:

作用一:runtime消息傳遞

可以先看一下 實例對象货邓、類對象、方法在runtime底層的表達形式:

//實例對象

struct objc_object {

Class isa;

};

//類對象

struct objc_class {

Class isa;

if !OBJC2

Class super_class;

const char *name;

long version;

long info;

long instance_size;

struct objc_ivar_list *ivars;

struct objc_method_list **methodLists;

struct objc_cache *cache;

struct objc_protocol_list *protocols;

endif

} ;

//方法列表

struct objc_method_list {

struct objc_method_list *obsolete;

int method_count;

ifdef LP64

int space;

endif

/* variable length structure */

struct objc_method method_list[1];

};

//方法

struct objc_method {

SEL method_name;

char *method_types;

IMP method_imp;

}

實例對象有isa指針 類對象有isa指針和superClass指針

對象調用方法 [obj foo] 的流程是:

底層運行時會被編譯器轉化為:objc_msgSend(obj, foo)

如果obj是實例對象.

第一步:obj通過isa指針找到Class

第二步:在Class的實例方法列表objc_method_list中查找有沒有foo

第三步:如果沒有四濒,就在Class的父類superClass中找换况。

第四步:一直找到根類RootClass。

如果obj是類對象

第一步: obj通過isa指針找到自己的元類meta-class

第二步:在meta-class的類方法類別中查找有沒有foo

第三步:如果沒有盗蟆,就在meta-superClass中找

第四步:一直找到meta-RootClass复隆。

如果每一次調用方法 都這么查找的話,涉及到效率問題姆涩。

所以出現(xiàn)了 objc_cache挽拂。 記錄緩存。這樣可以先在緩存中找骨饿。

CategoryRuntime 中是用結構體 category_t 來表示的亏栈,

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;//所有協(xié)議的列表

struct property_list_t *instanceProperties;//添加的所有屬性

} category_t;

也有很多人是這樣說的

typedef struct objc_category *Category;

struct objc_category {

char * _Nonnull category_name;

char * _Nonnull class_name;

struct objc_method_list * _Nullable instance_methods;

struct objc_method_list * _Nullable class_methods;

struct objc_protocol_list * _Nullable;

} ;

對比objc_class 可以發(fā)現(xiàn)objc_category中少了

struct objc_ivar_list * _Nullable ivars

也就是說沒有ivars數(shù)組.

變相的解釋了,分類為什么不能添加成員宏赘。

可以添加屬性绒北,但是這個屬性需要動態(tài)綁定,并且沒有生成對應成員變量察署。

關于分類的問題:

為什么分類的方法優(yōu)先級高于主類的方法闷游?

因為category_t中的方法列表是插入到主類方法列表前面。所以先執(zhí)行分類中的方法贴汪, 找到了對應的方法脐往,就會停止查找,這里就造成了一種覆蓋主類方法的假象扳埂。

category實現(xiàn)原理:

我們都知道OC所有的對象在運行時都是用結構體表示的业簿。 category也是,用category_t表示阳懂。

1梅尤、在編譯時期,將分類中實現(xiàn)的方法生成一個結構體 method_list_t 岩调、將聲明的屬性生成一個結構體 property_list_t 巷燥。然后通過這些結構體生成一個結構體 category_t 。

2号枕、然后將結構體 category_t 保存下來缰揪。

3、在運行時期堕澄,Runtime 會拿到編譯時期我們保存下來的結構體 category_t邀跃。

4霉咨、然后將結構體 category_t 中的實例方法列表、協(xié)議列表拍屑、屬性列表添加到主類中途戒。

5、將結構體 category_t 中的類方法列表 添加到主類的 metaClass 中

引申:可以給協(xié)議protocol添加屬性

需要在遵守協(xié)議的類中僵驰,實現(xiàn)屬性的setter喷斋,getter方法。

作用二:Runtime消息轉發(fā)

http://www.reibang.com/p/6ebda3cd8052

如果消息傳遞蒜茴,在方法列表中找不到對應的方法星爪。那么就會進入消息轉發(fā)。

從消息轉發(fā)到程序報錯前有三個機會:

1粉私、動態(tài)方法解析

2顽腾、備用接收者(快速轉發(fā))

3、完整消息轉發(fā)

動態(tài)方法解析

實現(xiàn)這兩個方法+ (BOOL)resolveInstanceMethod:(SEL)sel诺核;和 + (BOOL)resolveClassMethod:(SEL)sel;

  • (BOOL)resolveInstanceMethod:(SEL)sel {

if (sel == @selector(foo:)) {

class_addMethod([self class], sel, (IMP)fooMethod, "v@:");

return YES;

}

if (sel == @selector(fuu)) {

// 獲取 class

Class predicateMetaClass = objc_getClass([NSStringFromClass(self) UTF8String]);

// 根據(jù) class 獲取方法的實現(xiàn)

IMP impletor = class_getMethodImplementation(predicateMetaClass, @selector(hahaha));

// 獲取實例方法

Method predicateMethod = class_getInstanceMethod(predicateMetaClass, @selector(hahaha));

const char *encoding = method_getTypeEncoding(predicateMethod);

class_addMethod(predicateMetaClass, sel, impletor, encoding);

}

return [super resolveInstanceMethod:sel];

}

這里第一字符v代表函數(shù)返回類型void抄肖,第二個字符@代表self的類型id,第三個字符:代表_cmd的類型SEL窖杀。

void fooMethod(id obj, SEL _cmd) {

NSLog(@"Doing foo");//新的foo函數(shù)

}

  • (BOOL)resolveClassMethod:(SEL)sel {

if (sel == @selector(methodResolve)) {

// 獲取 MetaClass

Class predicateMetaClass = objc_getMetaClass([NSStringFromClass(self) UTF8String]);

// 根據(jù) metaClass 獲取方法的實現(xiàn)

IMP impletor = class_getMethodImplementation(predicateMetaClass, @selector(proxyMethod));

// 獲取類方法

Method predicateMethod = class_getClassMethod(predicateMetaClass, @selector(proxyMethod));

const char *encoding = method_getTypeEncoding(predicateMethod);

// 動態(tài)添加類方法

class_addMethod(predicateMetaClass, sel, impletor, encoding);

return YES;

}

return [super resolveClassMethod:sel];

}

  • (void)proxyMethod {

NSLog(@"---veryitman--- +proxyMethod of class's method for OC.");

}

備用接收者(快速轉發(fā))

  • (id)forwardingTargetForSelector:(SEL)aSelector {

if (aSelector == @selector(foo)) {

return [Person new];//返回Person對象漓摩,讓Person對象接收這個消息

}

return [super forwardingTargetForSelector:aSelector];

}

通過創(chuàng)建一個新的對象,讓這個新的對象去實現(xiàn)方法入客。

完整消息轉發(fā)

首先它會發(fā)送-methodSignatureForSelector:消息獲得函數(shù)的參數(shù)和返回值類型管毙。

如果-methodSignatureForSelector:返回nil ,

Runtime則會發(fā)出 -doesNotRecognizeSelector: 消息桌硫,程序這時也就掛掉了夭咬。

如果返回了一個函數(shù)簽名,Runtime就會創(chuàng)建一個NSInvocation 對象

并發(fā)送-forwardInvocation:消息給目標對象鞍泉。

  • (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {

if ([NSStringFromSelector(aSelector) isEqualToString:@"foo"]) {

return [NSMethodSignature signatureWithObjCTypes:"v@:"];//簽名皱埠,進入forwardInvocation

}

return [super methodSignatureForSelector:aSelector];

}

  • (void)forwardInvocation:(NSInvocation *)anInvocation {

SEL sel = anInvocation.selector;

Person *p = [Person new];

if([p respondsToSelector:sel]) {

[anInvocation invokeWithTarget:p];

}

else {

[self doesNotRecognizeSelector:sel];

}

}

第二步跟第三步的區(qū)別:

1、需要重載的API方法的用法不同

前者只需要重載一個API即可咖驮,后者需要重載兩個API。

前者只需在API方法里面返回一個新對象即可训枢,

后者需要對被轉發(fā)的消息進行重簽并手動轉發(fā)給新對象(利用 invokeWithTarget:)

2托修、轉發(fā)給新對象的個數(shù)不同

前者只能轉發(fā)一個對象,后者可以連續(xù)轉發(fā)給多個對象恒界。

-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector

{

if (aSelector==@selector(run)) {

return [NSMethodSignature signatureWithObjCTypes:"v@:"];

}

return [super methodSignatureForSelector: aSelector];

}

-(void)forwardInvocation:(NSInvocation *)anInvocation

{

SEL selector =[anInvocation selector];

RunPerson *RP1=[RunPerson new];

RunPerson *RP2=[RunPerson new];

if ([RP1 respondsToSelector:selector]) {

[anInvocation invokeWithTarget:RP1];

}

if ([RP2 respondsToSelector:selector]) {

[anInvocation invokeWithTarget:RP2];

}

}

Runtime在實際項目中的應用

1睦刃、關聯(lián)對象 : 給分類添加屬性

2、添加方法十酣,交換方法 :KVO實現(xiàn)

3涩拙、消息轉發(fā):熱更新JSPatch

4际长、實現(xiàn)NSCoding的自動歸檔和自動解檔

5、實現(xiàn)字典和模型的自動轉換

KVO的”isa-swizzling”技術兴泥,就是將指針原來指向本類工育,改成了指向中間類。

就是通過這個方法搓彻。

object_setClass(self, [SimpleKVO_Dog class]);

將self 改成 SimpleKVO_Dog類如绸。這樣isa指針指向SimpleKVO_Dog。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末旭贬,一起剝皮案震驚了整個濱河市怔接,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌稀轨,老刑警劉巖扼脐,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異奋刽,居然都是意外死亡谎势,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進店門杨名,熙熙樓的掌柜王于貴愁眉苦臉地迎上來脏榆,“玉大人,你說我怎么就攤上這事台谍⌒胛梗” “怎么了?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵趁蕊,是天一觀的道長坞生。 經常有香客問我,道長掷伙,這世上最難降的妖魔是什么是己? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮任柜,結果婚禮上卒废,老公的妹妹穿的比我還像新娘。我一直安慰自己宙地,他們只是感情好摔认,可當我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著宅粥,像睡著了一般参袱。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天抹蚀,我揣著相機與錄音剿牺,去河邊找鬼。 笑死环壤,一個胖子當著我的面吹牛晒来,可吹牛的內容都是我干的。 我是一名探鬼主播镐捧,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼潜索,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了懂酱?” 一聲冷哼從身側響起竹习,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎列牺,沒想到半個月后整陌,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡瞎领,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年泌辫,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片九默。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡震放,死狀恐怖,靈堂內的尸體忽然破棺而出驼修,到底是詐尸還是另有隱情殿遂,我是刑警寧澤,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布乙各,位于F島的核電站墨礁,受9級特大地震影響,放射性物質發(fā)生泄漏耳峦。R本人自食惡果不足惜恩静,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蹲坷。 院中可真熱鬧驶乾,春花似錦、人聲如沸冠句。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽懦底。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間聚唐,已是汗流浹背丐重。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留杆查,地道東北人扮惦。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像亲桦,于是被迫代替她去往敵國和親崖蜜。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,724評論 2 354

推薦閱讀更多精彩內容