iOS的Runtime講解與使用

一、Runtime簡介

object-c是基于C語言加入了面向?qū)ο筇匦院拖⑥D(zhuǎn)發(fā)機制的動態(tài)語言炮沐,除編譯器之外难咕,還需用Runtime系統(tǒng)來動態(tài)創(chuàng)建類和對象,進行消息發(fā)送和轉(zhuǎn)發(fā)理卑。

二翘紊、Runtime數(shù)據(jù)結(jié)構(gòu)

在Objective-C中,使用[receiver message]語法并不會馬上執(zhí)行receiver對象的message方法的代碼藐唠,而是向receiver發(fā)送一條message消息帆疟,這條消息可能由receiver來處理,也可能由轉(zhuǎn)發(fā)給其他對象來處理宇立,也有可能假裝沒有接收到這條消息而沒有處理踪宠。其實[receiver message]被編譯器轉(zhuǎn)化為:

id objc_msgSend ( id self, SEL op, ... );

下面從兩個數(shù)據(jù)結(jié)構(gòu)id和SEL來逐步分析和理解Runtime有哪些重要的數(shù)據(jù)結(jié)構(gòu)。

SEL

SEL是函數(shù)objc_msgSend第二個參數(shù)的數(shù)據(jù)類型妈嘹,表示方法選擇器柳琢,按下面路徑打開objc.h文件

typedef struct objc_selector *SEL;

其實它就是映射到方法的C字符串,你可以通過Objc編譯器命令@selector()或者Runtime系統(tǒng)的sel_registerName函數(shù)來獲取一個SEL類型的方法選擇器润脸。如果你知道selector對應(yīng)的方法名是什么柬脸,可以通過NSString* NSStringFromSelector(SEL aSelector)方法將SEL轉(zhuǎn)化為字符串,再用NSLog打印毙驯。

id

接下來看objc_msgSend第一個參數(shù)的數(shù)據(jù)類型id倒堕,id是通用類型指針,能夠表示任何對象爆价。按下面路徑打開objc.h文件:

/// Represents an instance of a class.

struct objc_object {

? ? Class isa? OBJC_ISA_AVAILABILITY;

};

/// A pointer to an instance of a class.

typedef struct objc_object *id;

id其實就是一個指向objc_object結(jié)構(gòu)體指針垦巴,它包含一個Class isa成員媳搪,根據(jù)isa指針就可以順藤摸瓜找到對象所屬的類。

Class

isa指針的數(shù)據(jù)類型是Class魂那,Class表示對象所屬的類蛾号,按下面路徑打開objc.h文件:

可以查看到Class其實就是一個objc_class結(jié)構(gòu)體指針,但這個頭文件找不到它的定義涯雅,需要在runtime.h才能找到objc_class結(jié)構(gòu)體的定義鲜结。

isa表示一個Class對象的Class,也就是Meta Class活逆。在面向?qū)ο笤O(shè)計中精刷,一切都是對象,Class在設(shè)計中本身也是一個對象蔗候。我們會在objc-runtime-new.h文件找到證據(jù)怒允,發(fā)現(xiàn)objc_class有以下定義:

struct objc_class : objc_object {

? // Class ISA;

? Class superclass;

? cache_t cache;? ? ? ? ? ? // formerly cache pointer and vtable

? class_data_bits_t bits;? ? // class_rw_t * plus custom rr/alloc flags

? ......

}

由此可見,結(jié)構(gòu)體objc_class也是繼承objc_object锈遥,說明Class在設(shè)計中本身也是一個對象纫事。

其實Meta Class也是一個Class,那么它也跟其他Class一樣有自己的isa和super_class指針所灸,關(guān)系如下:

super_class表示實例對象對應(yīng)的父類丽惶;

name表示類名;

ivars表示多個成員變量爬立,它指向objc_ivar_list結(jié)構(gòu)體钾唬。在runtime.h可以看到它的定義:

objc_ivar_list

其實就是一個鏈表,存儲多個objc_ivar侠驯,而objc_ivar結(jié)構(gòu)體存儲類的單個成員變量信息抡秆。

methodLists

表示方法列表,它指向objc_method_list結(jié)構(gòu)體的二級指針吟策,可以動態(tài)修改*methodLists的值來添加成員方法儒士,也是Category實現(xiàn)原理,同樣也解釋Category不能添加屬性的原因檩坚。在runtime.h可以看到它的定義:

同理着撩,objc_method_list也是一個鏈表,存儲多個objc_method效床,而objc_method結(jié)構(gòu)體存儲類的某個方法的信息。

cache用來緩存經(jīng)常訪問的方法权谁,它指向objc_cache結(jié)構(gòu)體剩檀,后面會重點講到。

protocols表示類遵循哪些協(xié)議旺芽。

Method表示類中的某個方法沪猴,在runtime.h文件中找到它的定義:

其實Method就是一個指向objc_method結(jié)構(gòu)體指針辐啄,它存儲了方法名(method_name)、方法類型(method_types)和方法實現(xiàn)(method_imp)等信息运嗜。而method_imp的數(shù)據(jù)類型是IMP壶辜,它是一個函數(shù)指針,后面會重點提及担租。

Ivar

Ivar表示類中的實例變量砸民,在runtime.h文件中找到它的定義:

Ivar其實就是一個指向objc_ivar結(jié)構(gòu)體指針,它包含了變量名(ivar_name)奋救、變量類型(ivar_type)等信息

IMP

在上面講Method時就說過岭参,IMP本質(zhì)上就是一個函數(shù)指針,指向方法的實現(xiàn)尝艘,在objc.h找到它的定義:

當(dāng)你向某個對象發(fā)送一條信息演侯,可以由這個函數(shù)指針來指定方法的實現(xiàn)背亥,它最終就會執(zhí)行那段代碼狡汉,這樣可以繞開消息傳遞階段而去執(zhí)行另一個方法實現(xiàn)轴猎。

Cache

顧名思義锐峭,Cache主要用來緩存,那它緩存什么呢椎扬?我們先在runtime.h文件看看它的定義:

Cache其實就是一個存儲Method的鏈表,主要是為了優(yōu)化方法調(diào)用的性能铣猩。當(dāng)對象receiver調(diào)用方法message時贿肩,首先根據(jù)對象receiver的isa指針查找到它對應(yīng)的類汰规,然后在類的methodLists中搜索方法,如果沒有找到茬射,就使用super_class指針到父類中的methodLists查找,一旦找到就調(diào)用方法。如果沒有找到朴读,有可能消息轉(zhuǎn)發(fā),也可能忽略它。但這樣查找方式效率太低惩琉,因為往往一個類大概只有20%的方法經(jīng)常被調(diào)用瞒渠,占總調(diào)用次數(shù)的80%。所以使用Cache來緩存經(jīng)常調(diào)用的方法,當(dāng)調(diào)用方法時仔燕,優(yōu)先在Cache查找晰搀,如果沒有找到乡翅,再到methodLists查找蠕蚜。

消息發(fā)送

前面從objc_msgSend作為入口腺毫,逐步深入分析Runtime的數(shù)據(jù)結(jié)構(gòu)邪蛔,了解每個數(shù)據(jù)結(jié)構(gòu)的作用和它們之間關(guān)系后侧到,我們正式轉(zhuǎn)入消息發(fā)送這個正題。

objc_msgSend函數(shù)

在前面已經(jīng)提過心软,當(dāng)某個對象使用語法[receiver message]來調(diào)用某個方法時踏堡,其實[receiver message]被編譯器轉(zhuǎn)化為:

id objc_msgSend ( id self, SEL op, ... );

首先根據(jù)receiver對象的isa指針獲取它對應(yīng)的class腐魂;

優(yōu)先在class的cache查找message方法,如果找不到甸箱,再到methodLists查找豪嗽;

如果沒有在class找到,再到super_class查找豌骏;

一旦找到message這個方法昵骤,就執(zhí)行它實現(xiàn)的IMP。

self與super


為了讓大家更好地理解self和super肯适,借用sunnyxx博客iOS程序員6級考試一道題目:下面的代碼分別輸出什么变秦?

@implementation Son : Father

- (id)init

{

? ? self = [super init];

? ? if (self)

? ? {

? ? ? ? NSLog(@"%@", NSStringFromClass([self class]));

? ? ? ? NSLog(@"%@", NSStringFromClass([super class]));

? ? }

? ? return self;

}

@end

self表示當(dāng)前這個類的對象,而super是一個編譯器標(biāo)示符框舔,和self指向同一個消息接受者蹦玫。在本例中,無論是[self class]還是[super class]刘绣,接受消息者都是Son對象樱溉,但super與self不同的是,self調(diào)用class方法時纬凤,是在子類Son中查找方法福贞,而super調(diào)用class方法時,是在父類Father中查找方法停士。

當(dāng)調(diào)用[self class]方法時挖帘,會轉(zhuǎn)化為objc_msgSend函數(shù),這個函數(shù)定義如下:

id objc_msgSend(id self, SEL op, ...)

這時會從當(dāng)前Son類的方法列表中查找恋技,如果沒有拇舀,就到Father類查找,還是沒有蜻底,最后在NSObject類查找到骄崩。我們可以從NSObject.mm文件中看到- (Class)class的實現(xiàn):

- (Class)class {

? ? return object_getClass(self);

}

所以NSLog(@"%@", NSStringFromClass([self class]));會輸出Son。

當(dāng)調(diào)用[super class]方法時,會轉(zhuǎn)化為objc_msgSendSuper要拂,這個函數(shù)定義如下:

id objc_msgSendSuper(struct objc_super *super, SEL op, ...)

objc_msgSendSuper函數(shù)第一個參數(shù)super的數(shù)據(jù)類型是一個指向objc_super的結(jié)構(gòu)體抠璃,從message.h文件中查看它的定義:

/// Specifies the superclass of an instance.

struct objc_super {

? ? /// Specifies an instance of a class.

? ? __unsafe_unretained id receiver;

? ? /// Specifies the particular superclass of the instance to message.

#if !defined(__cplusplus)? &&? !__OBJC2__

? ? /* For compatibility with old objc-runtime.h header */

? ? __unsafe_unretained Class class;

#else

? ? __unsafe_unretained Class super_class;

#endif

? ? /* super_class is the first class to search */

};

#endif

結(jié)構(gòu)體包含兩個成員,第一個是receiver脱惰,表示某個類的實例鸡典。第二個是super_class表示當(dāng)前類的父類。

這時首先會構(gòu)造出objc_super結(jié)構(gòu)體枪芒,這個結(jié)構(gòu)體第一個成員是self,第二個成員是(id)class_getSuperclass(objc_getClass("Son"))谁尸,實際上該函數(shù)會輸出Father舅踪。然后在Father類查找class方法,查找不到良蛮,最后在NSObject查到抽碌。此時,內(nèi)部使用objc_msgSend(objc_super->receiver, @selector(class))去調(diào)用决瞳,與[self class]調(diào)用相同货徙,所以結(jié)果還是Son。

三皮胡、使用示例

1痴颊、方法繼承

@interface CFURL : NSURL

+(instancetype)CFURLWithString:(NSString *)string;

@end

#import "CFURL.h"

@implementation CFURL

+(instancetype)CFURLWithString:(NSString *)string{

? ? CFURL *url = [super URLWithString:string];

? ? if (url == nil) {

? ? ? ? NSLog(@"url為空");

? ? }

? ? return url;

}

@end

//擴展分類

@interface NSURL (url)

+(instancetype)CF_URLWithStr:(NSString *)URLString;

@end

2、方法交換

Git下載:https://github.com/gaoguangxiao/RuntimeMCDemo

#import "NSURL+url.h"

#import@implementation NSURL (url)

+(void)load{

? ? //最早的方法屡贺,比main還早

? ? NSLog(@"load");

? ? //1.拿到兩個Method

? ? //2.進行方法交換

? ? Method m1 = class_getClassMethod([NSURL class], @selector(URLWithString:));

? ? Method m2 = class_getClassMethod([NSURL class], @selector(CF_URLWithStr:));

? ? //利用runtime進行方法的交換

? ? method_exchangeImplementations(m1, m2);

}

+(instancetype)CF_URLWithStr:(NSString *)URLString{

? ? //交換了兩個方法

? ? NSURL *url = [NSURL CF_URLWithStr:URLString];//注意這里不能再調(diào)用系統(tǒng)的方法

? ? if (!url) {

? ? ? ? NSLog(@"url為空");

? ? }

? ? return url;

}

@end

3蠢棱、分類擴展屬性

//.h文件#import"NSObject.h"

@interfaceNSObject(Property)//

@property在分類中只會生成set、get方法的聲明 不會生成實現(xiàn)甩栈,也不會生成_成員屬性@property(nonatomic,copy)NSStringname;

@end-----------------------------------------------

//.m文件

// 定義關(guān)聯(lián)的key

static const char*key ="name";

@implementation NSObject

(Property)

- (NSString*)name{

// 根據(jù)關(guān)聯(lián)的key泻仙,獲取關(guān)聯(lián)的值。

return objc_getAssociatedObject(self, key);

}

- (void)setName:(NSString*)name{/*

? ? 第一個參數(shù):給哪個對象添加關(guān)聯(lián)

? ? 第二個參數(shù):關(guān)聯(lián)的key量没,通過這個key獲取

? ? 第三個參數(shù):關(guān)聯(lián)的value

? ? 第四個參數(shù):關(guān)聯(lián)的策略

? */objc_setAssociatedObject(self, key, name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

}@end

4玉转、修改私有類屬性

//按鈕標(biāo)題的字體顏色

-(void)setTextColor:(UIColor?*)textColor

{

????_textColor?=?textColor;

?? ?unsignedintcount?=?0;

????Ivar?*ivars?=?class_copyIvarList([UIAlertAction?class],?&count);

????for(inti?=0;i?<?count;i?++){

?? ? ? ?Ivar?ivar?=?ivars[i];

????????NSString?*ivarName?=?[NSString?stringWithCString:ivar_getName(ivar)?encoding:NSUTF8StringEncoding];

?if([ivarName?isEqualToString:@"_titleTextColor"])?{

??[self?setValue:textColor?forKey:@"titleTextColor"];

????????}

?? ?}

}

四、參考網(wǎng)址

http://www.cocoachina.com/ios/20160914/17579.html?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末殴蹄,一起剝皮案震驚了整個濱河市究抓,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌袭灯,老刑警劉巖漩蟆,帶你破解...
    沈念sama閱讀 222,000評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異妓蛮,居然都是意外死亡怠李,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來捺癞,“玉大人夷蚊,你說我怎么就攤上這事∷杞椋” “怎么了惕鼓?”我有些...
    開封第一講書人閱讀 168,561評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長唐础。 經(jīng)常有香客問我箱歧,道長,這世上最難降的妖魔是什么一膨? 我笑而不...
    開封第一講書人閱讀 59,782評論 1 298
  • 正文 為了忘掉前任呀邢,我火速辦了婚禮,結(jié)果婚禮上豹绪,老公的妹妹穿的比我還像新娘价淌。我一直安慰自己,他們只是感情好瞒津,可當(dāng)我...
    茶點故事閱讀 68,798評論 6 397
  • 文/花漫 我一把揭開白布蝉衣。 她就那樣靜靜地躺著,像睡著了一般巷蚪。 火紅的嫁衣襯著肌膚如雪病毡。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,394評論 1 310
  • 那天屁柏,我揣著相機與錄音剪验,去河邊找鬼。 笑死前联,一個胖子當(dāng)著我的面吹牛功戚,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播似嗤,決...
    沈念sama閱讀 40,952評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼啸臀,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了烁落?” 一聲冷哼從身側(cè)響起乘粒,我...
    開封第一講書人閱讀 39,852評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎伤塌,沒想到半個月后灯萍,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,409評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡每聪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,483評論 3 341
  • 正文 我和宋清朗相戀三年旦棉,在試婚紗的時候發(fā)現(xiàn)自己被綠了齿风。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,615評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡绑洛,死狀恐怖救斑,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情真屯,我是刑警寧澤脸候,帶...
    沈念sama閱讀 36,303評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站绑蔫,受9級特大地震影響运沦,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜配深,卻給世界環(huán)境...
    茶點故事閱讀 41,979評論 3 334
  • 文/蒙蒙 一携添、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧凉馆,春花似錦、人聲如沸亡资。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽锥腻。三九已至嗦董,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間瘦黑,已是汗流浹背京革。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留幸斥,地道東北人匹摇。 一個月前我還...
    沈念sama閱讀 49,041評論 3 377
  • 正文 我出身青樓,卻偏偏與公主長得像甲葬,于是被迫代替她去往敵國和親廊勃。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,630評論 2 359

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

  • 我聽媽媽說昨天下午去聽知心姐姐講課啦经窖!我說昨天袋子里裝的什么呢坡垫?原來是四本書啊画侣!而且還是知心姐姐簽字的書...
    45cbff51831c閱讀 144評論 0 1
  • 黑夜來臨 笛聲孤寂 貓頭鷹用力屏住呼吸 搜索獵物的消息 蜻蜓走低 即將落雨 大地懂得天空的心意 隔空傳信 誰靜靜地...
    子瑜曰閱讀 121評論 1 3
  • 青春絢麗冰悠、多彩、華美配乱、略帶激情 讓人羨慕溉卓,更讓人敬畏 青春擁有自己獨一無二的氣息 一種讓人舞動的皮迟、神秘的 卻略帶淡...
    陳子瘋閱讀 133評論 0 1
  • 大唐是什么樣子?盛世代赁、開放扰她、交流、包容芭碍,還有李白徒役、白居易、楊貴妃……相信大家能夠給出很多自己對于中國唐代高度總結(jié)的...
    茶邊求閱讀 763評論 0 7
  • 我不是那種嗜書如命的人窖壕,所以也不像古人那樣讀書讀得廢寢忘食忧勿,更有甚者,挑燈夜戰(zhàn)瞻讽,頭懸粱針刺股鸳吸。 我有一個大學(xué)同學(xué)z...
    常今夏閱讀 301評論 0 0