前言
什么是運(yùn)行時(shí)(runtime)?
首先我們要先知道編程語(yǔ)言有靜態(tài)和動(dòng)態(tài)之分生百。所謂靜態(tài)語(yǔ)言递雀,就是在程序運(yùn)行前決定了所有的類(lèi)型判斷,類(lèi)的所有成員蚀浆、方法在編譯階段就確定好了內(nèi)存地址缀程。也就意味著所有類(lèi)對(duì)象只能訪(fǎng)問(wèn)屬于自己的成員變量和方法,否則編譯器直接報(bào)錯(cuò)市俊。比較常見(jiàn)的靜態(tài)的語(yǔ)言如:java杨凑,c++,c等等摆昧。
而動(dòng)態(tài)語(yǔ)言撩满,恰恰相反,類(lèi)型的判斷绅你、類(lèi)的成員變量鹦牛、方法的內(nèi)存地址都是在程序的運(yùn)行階段才最終確定,并且還能動(dòng)態(tài)的添加成員變量和方法勇吊。也就意味著你調(diào)用一個(gè)不存在的方法時(shí)曼追,編譯也能通過(guò),甚至一個(gè)對(duì)象它是什么類(lèi)型并不是表面我們所看到的那樣汉规,只有運(yùn)行之后才能決定其真正的類(lèi)型礼殊。相比于靜態(tài)語(yǔ)言,動(dòng)態(tài)語(yǔ)言具有較高的靈活性和可訂閱性针史。而oc晶伦,正是一門(mén)動(dòng)態(tài)語(yǔ)言。
介紹到這里啄枕,我想可以解釋一下運(yùn)行時(shí)是什么了婚陪?所謂運(yùn)行時(shí),就是程序在運(yùn)行時(shí)做的一些事频祝。蘋(píng)果提供了一套純c語(yǔ)言的api泌参,即runtime。在iOS開(kāi)發(fā)中runtime的特性使得oc這門(mén)語(yǔ)言具有獨(dú)特的魅力,我們可以利用運(yùn)行時(shí)處理一些特殊的事情常空,甚至你可以輕松的玩出一些逼格很高的花樣來(lái)沽一。下面就開(kāi)始一起進(jìn)入運(yùn)行時(shí)的世界吧。
在正式進(jìn)入篇幅之前漓糙,首先聲明一下铣缠,本編的主旨是簡(jiǎn)要闡述運(yùn)行時(shí)的一些機(jī)制和原理,重點(diǎn)是講述運(yùn)行時(shí)的一些常用用法,不會(huì)去深入探究底層的C語(yǔ)言api蝗蛙。
要了解運(yùn)行時(shí)蝇庭,我們得先了解oc的消息機(jī)制
那么什么是消息機(jī)制?
在Objective-C中捡硅,任何方法的調(diào)用遗契,本質(zhì)是發(fā)送消息。比如我們下面方法:
[obj ?method];
編譯器會(huì)自動(dòng)轉(zhuǎn)化為:
objc_msgSend(obj, @selector (method));
也就是說(shuō)我們?cè)趏c中調(diào)用任何一個(gè)方法病曾,其實(shí)質(zhì)是轉(zhuǎn)換為runtime中的一個(gè)函數(shù)objc_msgSend()牍蜂,這個(gè)函數(shù)的作用是向obj對(duì)象(方法的調(diào)用者)發(fā)送了一條消息,告訴它你該去執(zhí)行某個(gè)方法泰涂。
所以鲫竞,我們其實(shí)也可以直接用運(yùn)行時(shí)去調(diào)用你想要調(diào)用的任何一個(gè)可調(diào)用的方法:
如:
Dog? *dog = [Dog alloc] init];
[dog run:100];
等價(jià)于:
Dog? *dog? = objc_msgSend(objc_getClass("Dog"), @selector(alloc));
dog = objc_msgSend(dog, sel_registerName("init"));
objc_msgSend(dog, sel_registerName("run:"),100); //調(diào)用帶參數(shù)的方法
注:使用objc_msgSend()函數(shù)逼蒙,須要先import
講到這里从绘,我們就可以說(shuō)一說(shuō)什么是oc消息機(jī)制,也就是一個(gè)方法的調(diào)用流程是牢。
1僵井、編譯器會(huì)先將代碼[obj? method]轉(zhuǎn)化為objc_msgSend(obj, @selector (method))函數(shù)去執(zhí)行。
2驳棱、在objc_msgSend()函數(shù)中,首先通過(guò)obj的isa指針找到(對(duì)象)obj對(duì)應(yīng)的(類(lèi))class批什。
3、在class中會(huì)先去cache中 通過(guò)SEL查找對(duì)應(yīng)函數(shù)method(cache中method列表是以SEL為key通過(guò)hash表來(lái)存儲(chǔ)的社搅,這樣能提高函數(shù)查找速度)驻债,若 cache中未找到。再去class中的消息列表methodList中查找形葬,若methodlist中未找到合呐,則取superClass中查找。若能找到笙以,則將method加 入到cache中淌实,以方便下次查找,并通過(guò)method中的函數(shù)指針跳轉(zhuǎn)到對(duì)應(yīng)的函數(shù)中去執(zhí)行猖腕。
補(bǔ)充:
>在oc中拆祈,每一個(gè)對(duì)象都有一個(gè)isa指針變量,這個(gè)指針指向的是對(duì)象的類(lèi)谈息,我們可以通過(guò)isa指針訪(fǎng)問(wèn)一個(gè)對(duì)象的類(lèi)
>方法都保存在類(lèi)的消息列表中缘屹,這個(gè)列表其實(shí)是一個(gè)字典,key是selector侠仇,value是IMP(imp是一個(gè)指針類(lèi)型,指向方法的實(shí)現(xiàn)),并且selector和IMP之間的關(guān)系是在運(yùn)行時(shí)才決定的逻炊,而不是編譯時(shí)互亮。如此們就可以做出一些特別事情來(lái)。
我們可以用運(yùn)行時(shí)做什么
1余素、互換方法的實(shí)現(xiàn)
上面說(shuō)到selector和IMP之間的關(guān)系是在運(yùn)行時(shí)才決定的豹休,那我們是不是可以改變selector和IMP的對(duì)應(yīng)關(guān)系呢?runtime就給我們提供了這么一個(gè)函數(shù):
void method_exchangeImplementations(Method m1, Method m2)
我們可以通過(guò)此函數(shù)交換兩個(gè)方法的實(shí)現(xiàn)桨吊,在開(kāi)發(fā)中威根,可能我們會(huì)經(jīng)常遇到一種場(chǎng)景,想為系統(tǒng)的某個(gè)方法增加一些特定的功能视乐,又不想改變?cè)械臇|西洛搀,想要做到無(wú)縫銜接,用runtime方法互換無(wú)疑是最完美的佑淀。下面以交換系統(tǒng)的dealloc方法的實(shí)現(xiàn)為例:
首先建一個(gè)NSObject類(lèi)目NSObject+ExchangeMethod留美,在類(lèi)目中為NSObject類(lèi)擴(kuò)展一個(gè)my_dealloc方法用于替換系統(tǒng)的dealloc方法,其.m文件實(shí)現(xiàn)如下:
這樣伸刃,當(dāng)一個(gè)類(lèi)的dealloc方法被調(diào)用時(shí)谎砾,會(huì)執(zhí)行my_dealloc方法里的實(shí)現(xiàn),完全無(wú)需再對(duì)原有的代碼做任何改動(dòng)
2捧颅、動(dòng)態(tài)添加方法
前面有說(shuō)到景图,動(dòng)態(tài)語(yǔ)言調(diào)用一個(gè)沒(méi)有的方法時(shí),編譯階段也不不會(huì)報(bào)錯(cuò)碉哑。比如:
Dog *dog = [Dog alloc] init];
[dog performSelector:@selector(eat)]];
注:dog類(lèi)中沒(méi)有聲明也沒(méi)實(shí)現(xiàn)eat方法
上面代碼症歇,編譯階段肯定會(huì)通過(guò),但程序一運(yùn)行時(shí)便直接拋出異常閃退谭梗,拋出異常的打印閉著眼睛也知道是 :-[Dog eat]: unrecognized selector sent to instance 0x7fac91d0eba0'忘晤。這也印證了動(dòng)態(tài)語(yǔ)言的方法需要在運(yùn)行階段才最終確定。
從而激捏,我們可以動(dòng)態(tài)的為某個(gè)類(lèi)添加方法设塔,而蘋(píng)果performSelector:這個(gè)方法也很好的為我們逃過(guò)編譯報(bào)錯(cuò)提供了支持。示例代碼如下:
屆時(shí)远舅,我們?cè)谌缟厦嬲{(diào)用[dog performSelector:@selector(eat)]]時(shí)闰蛔,就會(huì)去執(zhí)行test函數(shù)了。
3图柏、動(dòng)態(tài)添加屬性
這也是runtime的一個(gè)重量級(jí)功能了序六,我們經(jīng)常會(huì)想為系統(tǒng)的類(lèi)或者一些不便修改的第三方框架的類(lèi)增加一些自定義的屬性以滿(mǎn)足開(kāi)發(fā)的需求。這個(gè)時(shí)候我們還是首先會(huì)想到類(lèi)目蚤吹,但是問(wèn)題來(lái)了例诀,類(lèi)目只能為一個(gè)類(lèi)添加方法随抠,不能添加屬性。
怎么做呢繁涂,還是用到運(yùn)行時(shí)拱她,為類(lèi)動(dòng)態(tài)添加屬性。示例代碼:為NSobject類(lèi)添加一個(gè)字符串類(lèi)型的屬性: NSString *name
首先我們還是為NSobject建一個(gè)類(lèi)目扔罪,其.h文件如下:
這里我們用property秉沼,類(lèi)目中用 property會(huì)自動(dòng)生成set/get的聲明,但是沒(méi)有實(shí)現(xiàn)矿酵,也無(wú)法生成下劃線(xiàn)的成員變量唬复,我們需要手動(dòng)實(shí)現(xiàn)set、get方法全肮。其.m文件如下:
4敞咧、獲取類(lèi)中所有的成員變量和屬性
在開(kāi)發(fā)中,你可能會(huì)遇到想要改變系統(tǒng)自帶的類(lèi)的某一個(gè)值倔矾,卻找不與之對(duì)應(yīng)的api妄均,然后你就在那找瞎了眼,找呀找哪自,始終找不到丰包。這個(gè)時(shí)候我們可以確定一點(diǎn),蘋(píng)果系統(tǒng)自帶的類(lèi)有很多私有的屬性或成員變量沒(méi)有公開(kāi)出來(lái)壤巷,也就意味著蘋(píng)果它不想讓我們?cè)L問(wèn)邑彪。我靠,那還搞毛胧华,有時(shí)需求來(lái)了寄症,我還非要訪(fǎng)問(wèn)不可,那怎么辦矩动?
用運(yùn)行時(shí)獲取類(lèi)的所有成員變量有巧,即便私有的也能獲取的到,用的函數(shù)如下:
Ivar *class_copyIvarList(Class cls, unsigned int *outCount)//獲取類(lèi)中所有的成員變量
objc_property_t class_getProperty(Class cls, const char *name)//獲取類(lèi)中所有的屬性
代碼示例如下:
使用運(yùn)行時(shí)獲取類(lèi)中所有成員變量悲没,還是相當(dāng)有用的篮迎,比如現(xiàn)在一些字典轉(zhuǎn)模型框架,它需要獲取到模型的所有屬性名示姿,以這個(gè)屬性名為key甜橱,取到字典中對(duì)應(yīng)的value,然后通過(guò)kvc給這個(gè)屬性賦設(shè)置值栈戳。再比如岂傲,你要設(shè)置系統(tǒng)UITextField控件placeholder的顏色,你會(huì)發(fā)現(xiàn)你翻遍api也找不到一個(gè)屬性和方法來(lái)設(shè)置子檀,這時(shí)你用運(yùn)行時(shí)獲取UITextField類(lèi)所有成員變量镊掖,你會(huì)發(fā)現(xiàn)有一個(gè)_placeholderLabel成員變量乃戈,我們只需:
[self.textField setValue:[UIColor blueColor] forKeyPath:@"_placeholderLabel.textColor"];
總結(jié)
以上便是運(yùn)行時(shí)的一些常用用法,本文僅拋磚引玉堰乔,在ios開(kāi)發(fā)的道路上偏化,想要深入了解oc這門(mén)語(yǔ)言脐恩,runtime是一餐不容錯(cuò)過(guò)的盛宴镐侯。