OC,objc_msgSend()函數(shù)做了哪些事

OC,objc_msgSend()

簡介:用一句話簡介消息傳遞那就是:用一個 C語言函數(shù)欢搜,向一個實例傳遞一個字符串封豪,實例拿到字符串后與自己的method_list中的SEL比較,遇到一樣的就找到對應(yīng)IMP執(zhí)行炒瘟。SEL就是一個字符串吹埠,IMP就是一個函數(shù)指針,在一個Method結(jié)構(gòu)體中封裝著這兩者疮装,因此他倆一一對應(yīng)缘琅。

  • 在向一個實例發(fā)送消息時[obj doSomthing];實際上是調(diào)用的這個函數(shù)objc_msgSend(obj,@selector(doSomething));(該函數(shù)在<objc/message.g>中)
  • 接下來將從頭到尾表述在這個函數(shù)中究竟干了多少事情。
  • 階段1(對于能獨(dú)立解決的問題):
  1. 尋找類:根據(jù)上一篇的OC是如何使C語言變得面向?qū)ο蟮睦疲酪粋€objc_object結(jié)構(gòu)體中就只有isa(一個指向objc_class的指針)刷袍,因此需要從他的objc_class結(jié)構(gòu)體中查找函數(shù),于是順著isa指針找到了這個objc_class樊展。
  2. 訪問cache:objc_cache結(jié)構(gòu)體中裝著一個method結(jié)構(gòu)體的數(shù)組呻纹,先在這個數(shù)組中遍歷method,對于每一個method专缠,要對照method中的SEL與上面?zhèn)鬟f進(jìn)來的@selector(doSomething)是不是一樣雷酪,如果一樣就確認(rèn)是需要調(diào)用它的IMP。
  3. 訪問method_list:如果在步驟2中沒有找到一樣的SEL藤肢,就需要遍歷所有的函數(shù)了太闺,與遍歷cache時一樣,對照SEL嘁圈,選取IMP省骂。如果在這個步驟中仍然不能找到對應(yīng)的SEL蟀淮,那么就會進(jìn)入階段2(不能獨(dú)立解決的問題)。
  4. 假如通過上述1-3過程找到了method钞澳,之后執(zhí)行對應(yīng)的IMP就可以了怠惶。
  • 階段2(對于不能獨(dú)立解決的問題):
  1. 尋找父類:在階段1中,當(dāng)在cache和method_list中不能找到一致的SEL時轧粟,msgSend函數(shù)會繼續(xù)往祖墳上刨策治,它根據(jù)objc_class中的super指針(指向父類的objc_class結(jié)構(gòu)體的指針)找到該類的父類,然后在父類的cache和method_list中執(zhí)行階段1的2兰吟、3步一樣的操作通惫。
  2. 尋找父類的父類:如果在這個時候還是不能找到對應(yīng)的SEL,就會繼續(xù)根據(jù)super指針繼續(xù)尋找父類混蔼,直到super指針是nil履腋,說明沒有一個人可以響應(yīng)這條消息。這個時候如果沒有一些階段3的防護(hù)措施就會報錯了惭嚣,通常是unrecognized selector的錯誤遵湖。
  • 階段3(所有子類和父類都不能解決的問題):
  • 對于我們沒有預(yù)測到的一些錯誤調(diào)用(使用performSelector傳入一個隨便的selector)或者在實現(xiàn)上的一些失誤(比如你答應(yīng)了某個協(xié)議去實現(xiàn)他的一些方法,然后在implement中沒有寫對應(yīng)的實現(xiàn)晚吞,而協(xié)議的另一端正在調(diào)用協(xié)議中的方法)延旧。有三種補(bǔ)救方式允許我們避免程序的崩潰:在resolve(Instance/Class)Method中做檢查、提供另外一個可以供轉(zhuǎn)發(fā)的對象槽地、使用NSInvocation重新調(diào)用迁沫。
  1. 在resolveMethod中做檢查(這里均以實例方法為例):首先進(jìn)入resolveInstanceMethod函數(shù),注意這個方法不會影響階段1和階段2的所有過程捌蚊,也就是說弯洗,執(zhí)行了這個方法之后,如果仍然找不到對應(yīng)的SEL逢勾,依然是會報錯的牡整。因此我們可以在這個方法中,動態(tài)地添加其實現(xiàn)溺拱。創(chuàng)建TestObj類如下逃贝,啥屬性啥方法都沒有,卻能向他發(fā)送任何實例消息迫摔。原因就是沐扳,在resolveInstanceMethod方法中接收到一個找不到的SEL,不論這個SEL叫什么句占,我們都給它一個默認(rèn)的實現(xiàn)defaultDealMethod沪摄。因此,TestObj永遠(yuǎn)都不會引起崩潰。

#import <Foundation/Foundation.h>

@interface TestObj : NSObject

@end

#import "TestObj.h"
#import <objc/runtime.h>

@implementation TestObj

+ (BOOL)resolveInstanceMethod:(SEL)sel{
    NSString *str = NSStringFromSelector(sel);
    NSLog(@"TestObj沒有找到方法:%@杨拐,來到了resolveMethod中",str);
    class_addMethod(self, sel, (IMP)defaultDealMethod, "v@:");
    return YES;
}

void defaultDealMethod(id self, SEL sel){
    NSString *str = NSStringFromSelector(sel);
    NSLog(@"DefaultDealMethod:%@",str);
}
@end
  1. 第二個保障祈餐,就是為這個不能處理的對象找一個可以求助的實例。為了告知這個實例是誰哄陶,需要重寫方法forwardingTargetForSelector:(SEL)sel方法帆阳。既然剛剛有一個無所不能的TestObj方法,那就讓他來擔(dān)當(dāng)這個求助對象吧屋吨,雖然他沒做什么實質(zhì)工作蜒谤,但能保證不崩潰。我們在測試的ViewController中這樣寫至扰,雖然隨便調(diào)用了兩個沒有實現(xiàn)的方法鳍徽,但是并沒有崩潰,因為他成功向TestObj求助了敢课,而TestObj為了幫助它旬盯,在自己的方法列表中加入了兩個新的方法,這樣就可以查找到SEL并成功執(zhí)行翎猛。
- (void)viewDidLoad {
    [super viewDidLoad];
    objc_msgSend(self, @selector(asdasdasdadsas));
    objc_msgSend(self, @selector(asdakjsdajdjka));
}

- (id)forwardingTargetForSelector:(SEL)aSelector{
    return [[TestObj alloc] init];
}

3.第三個保障:使用Invocation調(diào)用(個人不大明白這樣做的必要性),Invocation類似設(shè)計模式中的命令模式接剩,就是將一個命令或者是調(diào)用封裝為一個Obj切厘,Invocation中大體包含如下屬性:一個MethodSignature(格式)、target(目標(biāo)懊缺,也就是消息發(fā)送的對象)疫稿、selector(方法名);一個MethodSignature中又大體包含如下屬性:參數(shù)個數(shù)鹃两、方法長度遗座、返回值類型、返回值長度俊扳。由此可見途蒋,一個Invocation就是對一次調(diào)用函數(shù)各方面格式、目標(biāo)的封裝馋记。因此我們需要做的就是在最后一條保障中号坡,規(guī)定一個Invocation,并使用這個Invocation完成調(diào)用梯醒。

- (id)forwardingTargetForSelector:(SEL)aSelector{
    //return [[TestObj alloc] init];
    return nil;
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
    return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation{
    [anInvocation invokeWithTarget:[[TestObj alloc] init]];
}
  • Signature是Invocation的組成元素宽堆,因此在得到Invocation前需獲取到這些格式信息,對于這個格式:第一位是返回值茸习,可以是c(char)畜隶、i(int)、s(short)、l(long)等等籽慢。第二位是接收的第一個參數(shù)self浸遗,因此是"@",第三位是selector用":"表示嗡综,之后的位置就都是自定義的參數(shù)了乙帮,這些類型在官網(wǎng)上可以查到,后面給出一些常用的极景。舉個例子察净,這樣一個函數(shù):
- (instancetype)initWithName:(NSString *)name height:(float)height weight:(float)weight father:(id)father mother:(id)mother;

  • 它的格式就可以這樣表示"@@:@ff@@";
  • 每一位分別對應(yīng) instance返回值、默認(rèn)參數(shù)self盼樟、默認(rèn)參數(shù)SEL氢卡、參數(shù)name、參數(shù)height晨缴、參數(shù)weight译秦、參數(shù)father、參數(shù)mother击碗。
char:c
int:i
short:s
long:l
longlong:q
//上面這些都大寫就代表是 unsigned的,如 unsigned int:I
float:f
double:d
bool:B
void:v
id:@
SEL: :
  • 如果我們在methodSignature中返回了一個非nil的signature筑悴,系統(tǒng)就會為我們創(chuàng)建一個invocation并調(diào)用forwardingInvocation方法,在該方法中我們會得到之前預(yù)定好的格式的invocation稍途,我們就可以invoke這個invocation了阁吝。

  • 當(dāng)然,如果最后第三個保障也沒有好好利用的話就只有崩潰了械拍。

另外突勇,如果xcode不允許使用帶參數(shù)的msgSend()函數(shù),在項目的buildSettings中搜索strict坷虑,有一個Enable strict checking of objc_msgSend call選項甲馋,把他置為NO,之后就好用了迄损。

以上就是OC消息傳遞的全部過程

結(jié)束

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末定躏,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子芹敌,更是在濱河造成了極大的恐慌共屈,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,599評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件党窜,死亡現(xiàn)場離奇詭異拗引,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)幌衣,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,629評論 3 385
  • 文/潘曉璐 我一進(jìn)店門矾削,熙熙樓的掌柜王于貴愁眉苦臉地迎上來壤玫,“玉大人,你說我怎么就攤上這事哼凯∮洌” “怎么了?”我有些...
    開封第一講書人閱讀 158,084評論 0 348
  • 文/不壞的土叔 我叫張陵断部,是天一觀的道長猎贴。 經(jīng)常有香客問我,道長蝴光,這世上最難降的妖魔是什么她渴? 我笑而不...
    開封第一講書人閱讀 56,708評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮蔑祟,結(jié)果婚禮上趁耗,老公的妹妹穿的比我還像新娘。我一直安慰自己疆虚,他們只是感情好苛败,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,813評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著径簿,像睡著了一般罢屈。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上篇亭,一...
    開封第一講書人閱讀 50,021評論 1 291
  • 那天缠捌,我揣著相機(jī)與錄音,去河邊找鬼暗赶。 笑死,一個胖子當(dāng)著我的面吹牛肃叶,可吹牛的內(nèi)容都是我干的蹂随。 我是一名探鬼主播,決...
    沈念sama閱讀 39,120評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼因惭,長吁一口氣:“原來是場噩夢啊……” “哼岳锁!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蹦魔,我...
    開封第一講書人閱讀 37,866評論 0 268
  • 序言:老撾萬榮一對情侶失蹤激率,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后勿决,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體乒躺,經(jīng)...
    沈念sama閱讀 44,308評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,633評論 2 327
  • 正文 我和宋清朗相戀三年低缩,在試婚紗的時候發(fā)現(xiàn)自己被綠了嘉冒。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片曹货。...
    茶點(diǎn)故事閱讀 38,768評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖讳推,靈堂內(nèi)的尸體忽然破棺而出顶籽,到底是詐尸還是另有隱情,我是刑警寧澤银觅,帶...
    沈念sama閱讀 34,461評論 4 333
  • 正文 年R本政府宣布礼饱,位于F島的核電站,受9級特大地震影響究驴,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜纳胧,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,094評論 3 317
  • 文/蒙蒙 一镰吆、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧跑慕,春花似錦、人聲如沸核行。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,850評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽减余。三九已至,卻和暖如春惩系,著一層夾襖步出監(jiān)牢的瞬間位岔,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,082評論 1 267
  • 我被黑心中介騙來泰國打工抒抬, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人晤柄。 一個月前我還...
    沈念sama閱讀 46,571評論 2 362
  • 正文 我出身青樓擦剑,卻偏偏與公主長得像芥颈,于是被迫代替她去往敵國和親惠勒。 傳聞我的和親對象是個殘疾皇子爬坑,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,666評論 2 350

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

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,690評論 0 9
  • 消息發(fā)送和轉(zhuǎn)發(fā)流程可以概括為:消息發(fā)送(Messaging)是 Runtime 通過 selector 快速查找 ...
    lylaut閱讀 1,827評論 2 3
  • 關(guān)于OC中的消息發(fā)送的實現(xiàn)盾计,在去年也看過一次巾遭,當(dāng)時有點(diǎn)不太理解肉康,但是今年再看卻很容易理解灼舍。 我想這跟知識體系的構(gòu)建...
    咖啡綠茶1991閱讀 942評論 0 1
  • 我們常常會聽說 Objective-C 是一門動態(tài)語言,那么這個「動態(tài)」表現(xiàn)在哪呢骑素?我想最主要的表現(xiàn)就是 Obje...
    Ethan_Struggle閱讀 2,186評論 0 7
  • 《時空之局炫乓,風(fēng)水之局,大運(yùn)之局》 也許每座城市的規(guī)劃與發(fā)展都有著自己注定的風(fēng)水之局献丑,也許每個地區(qū)的規(guī)劃與發(fā)...
    眾心無相閱讀 4,735評論 0 1