溫故知新SEL/MethodSignature/Invocation

  • SEL是方法或者函數(shù)指針嗎紧唱?
  • 方法簽名是什么活尊,有什么用處?
  • 為什么方法轉(zhuǎn)發(fā)需要先返回一個(gè)方法簽名漏益?
  • 除了runtime方法外你會(huì)如何調(diào)用私有方法蛹锰?
  • 為什么OC沒有方法重載的概念?

iOS開發(fā)中我們整日跟方法打交道绰疤,我們都知道它最后都是發(fā)送該消息铜犬,它用起來足夠簡(jiǎn)單,但對(duì)于方法調(diào)用涉及到的一些知識(shí)和概念我覺得有必要再次認(rèn)識(shí)一下轻庆,接下來的篇幅我將要介紹

  • SEL
  • IMP
  • Method
  • NSMethodSignature
  • NSInvocation

SEL只是方法的名稱

在運(yùn)行時(shí)objc.h中可以看到如下定義癣猾,SEL是一個(gè)指向objc_selector結(jié)構(gòu)體的指針,SEL可以看似是人的名字

/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;

在運(yùn)行時(shí)源碼里面我們并沒有看到objc_selector結(jié)構(gòu)體的具體實(shí)現(xiàn)余爆,但根據(jù)我們的打印數(shù)據(jù)我們可以認(rèn)為objc_selector內(nèi)部至少包含一個(gè)c字符串的字段纷宇,可能還包含其他用來加快SEL查找的輔助字段。

  • SEL的三種創(chuàng)建方式
SEL s1 = @selector(todo);
SEL s2 = NSSelectorFromString(@"todo");
SEL s3 = sel_registerName("todo");
NSLog(@"s1 %p", s1);
NSLog(@"s2 %p", s2);
NSLog(@"s3 %p", s3);

[3057:414576] s1 0x10b0ee595
[3057:414576] s2 0x10b0ee595
[3057:414576] s3 0x10b0ee595
打印輸出三個(gè)SEL變量的值蛾方,可以看到三個(gè)變量的值是一樣的像捶,這是因?yàn)?code>SEL是存儲(chǔ)在靜態(tài)數(shù)據(jù)區(qū),像字符串常量一樣只要是名稱一樣的方法他們的sel都會(huì)是同一份內(nèi)存桩砰,所以SEL并不依賴于方法而存在作岖,可以創(chuàng)建一個(gè)SEL但是整個(gè)App可以沒這個(gè)方法存在。

即使來自不同類的或者不同模塊的五芝,只要方法名稱相同(比如'setName:'就是一個(gè)方法名稱),那么這些方法的SEL的值都一樣辕万,所有的SEL都會(huì)由全局變量來維護(hù)枢步;使用@selector宏的方式創(chuàng)建SEL的一個(gè)好處是在編碼階段它會(huì)在當(dāng)前上下文環(huán)境中尋找對(duì)應(yīng)的名稱的方法,所以如果查找不到該方法會(huì)則Xcode會(huì)給出警告提示渐尿;

IMP 是方法的函數(shù)指針

IMP定義如下:

#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ ); 
#else
typedef id (*IMP)(id, SEL, ...); 
#endif

可以看到IMP是一個(gè)擁有多參數(shù)的函數(shù)指針醉途,OC中的所有的方法調(diào)用最后都將轉(zhuǎn)換為IMP指針指向的函數(shù)調(diào)用

Method是SEL和IMP的一個(gè)映射關(guān)系的包裝

  • Method定義
typedef struct old_method *Method;

struct objc_method {
    SEL method_name                                          OBJC2_UNAVAILABLE;
    char *method_types                                       OBJC2_UNAVAILABLE;
    IMP method_imp                                           OBJC2_UNAVAILABLE;
}                                                            OBJC2_UNAVAILABLE;
  • 方法的獲取
Method class_getInstanceMethod(Class cls, SEL sel)        
  • 從方法列表中查找方法過程
static inline old_method *_findMethodInList(old_method_list * mlist, SEL sel) {
    int i;
    if (!mlist) return nil;
    for (i = 0; i < mlist->method_count; i++) {
        old_method *m = &mlist->method_list[i];
        if (m->method_name == sel) {
            return m;
        }
    }
    return nil;
}

解析:在類的結(jié)構(gòu)中包含有它的實(shí)例方法列表(Method列表),而Method的結(jié)構(gòu)體中包含方法選擇子SEL和方法實(shí)現(xiàn)IMP砖茸。 runtime的方法查找過程隘擎,簡(jiǎn)化起來就是根據(jù)SEL在類的方法列表中遍歷查找與之匹配的SEL的方法。

這種查找過程也決定了OC語言不支持方法重載這個(gè)特性凉夯。如果支持重載货葬,包含不止一個(gè)同名方法,那么該方法的查找只會(huì)找到第一個(gè)就直接返回劲够,因?yàn)楦鶕?jù)SEL不能確定是查找哪個(gè)重載方法震桶;
  • 至此我們能回答"為什么OC沒有方法重載?"這個(gè)問題了

重載方法是【方法名稱一樣征绎,但是參數(shù)個(gè)數(shù)蹲姐,參數(shù)類型不一樣的方法】,在這樣的前提下,我們要確定一個(gè)方法就得需要方法名稱SEL和方法參數(shù)類型描述method_types這兩個(gè)條件了柴墩,上面方法查找的過程_findMethodInList我們可以看到運(yùn)行時(shí)查找方法僅僅根據(jù)方法名SEL來查找的if (m->method_name == sel)忙厌,所以O(shè)C目前是不支持方法重載這個(gè)很多語言都有的特性的;

NSMethodSignature是對(duì)方法參數(shù)的描述

  • 方法簽名的本質(zhì)
+ (nullable NSMethodSignature *)signatureWithObjCTypes:(const char *)types;

從方法簽名的生成方法可以看到方法簽名只需要一個(gè)類型參數(shù)字符串就可以構(gòu)造江咳,這個(gè)類型參數(shù)是OC中通用的類型編碼逢净,字符串格式為:返回參數(shù)類型參數(shù)1類型參數(shù)2類型,比如setName:(NSString *)name的類型字符串為v@:@

  • 類型編碼 type Encodings

runtime中為了表達(dá)方便扎阶,對(duì)各種數(shù)據(jù)類型的表示進(jìn)行了編碼汹胃,所有的類型都可以用一個(gè)對(duì)應(yīng)的字符來表示,其中v表示void類型东臀,@表示OC對(duì)象類型着饥,:表示SEL類型,具體可參考官方文檔Type Encodings

// 類型編碼枚舉
enum _NSObjCValueType {
    NSObjCNoType = 0,
    NSObjCVoidType = 'v',
    NSObjCCharType = 'c',
    NSObjCShortType = 's',
    NSObjCLongType = 'l',
    NSObjCLonglongType = 'q',
    NSObjCFloatType = 'f',
    NSObjCDoubleType = 'd',
    NSObjCBoolType = 'B',
    NSObjCSelectorType = ':',
    NSObjCObjectType = '@',
    NSObjCStructType = '{',
    NSObjCPointerType = '^',
    NSObjCStringType = '*',
    NSObjCArrayType = '[',
    NSObjCUnionType = '(',
    NSObjCBitfield = 'b'
}

// 練習(xí)一下類型編碼
// - (void)setName:(NSString *)name; 
// - (NSString *)name; 
// - (void)downloadImage:(NSString *)url completionHandler:^(void(^)(UIImage *image))completionHandler; 

// v@:@
// @@:
// v@:@^
// 測(cè)試代碼
NSMethodSignature *methodSignature1 = [NSMethodSignature signatureWithObjCTypes:"v@:"];
NSMethodSignature *methodSignature2 = [NSMethodSignature signatureWithObjCTypes:"v@:"];
NSLog(@"methodSignature1 %p", methodSignature1);
NSLog(@"methodSignature2 %p", methodSignature2);

// 輸出
// 2018-01-29 22:18:34.622 IANLearn[3057:66145] methodSignature1 0x608000266ec0
// 2018-01-29 22:18:34.622 IANLearn[3057:66145] methodSignature2 0x608000266ec0
方法簽名是用來表達(dá)一個(gè)方法的參數(shù)特征惰赋,這些特征包含方法的參數(shù)個(gè)數(shù)宰掉,參數(shù)類型,返回值類型和SEL一樣赁濒,所以只要是方法的參數(shù)特性(參數(shù)和返回值)一樣轨奄,那么方法的簽名就一樣,所有的方法簽名都會(huì)由全局變量來維護(hù)
  • NSMethodSignature查找
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
+ (NSMethodSignature *)instanceMethodSignatureForSelector:(SEL)aSelector;

// 測(cè)試代碼
NSMethodSignature *methodSignature3 = [self methodSignatureForSelector:@selector(application:didFinishLaunchingWithOptions:)];
NSMethodSignature *methodSignature4 = [AppDelegate instanceMethodSignatureForSelector:@selector(application:didFinishLaunchingWithOptions:)];
NSMethodSignature *methodSignature5 = [AppDelegate instanceMethodSignatureForSelector:@selector(unknowMethod)];
NSLog(@"methodSignature3 %p", methodSignature3);
NSLog(@"methodSignature4 %p", methodSignature4);
NSLog(@"methodSignature5 %p", methodSignature5);

// 輸出
// 2018-01-29 22:28:11.792 IANLearn[3207:70694] methodSignature3 0x600000263900
// 2018-01-29 22:28:11.792 IANLearn[3207:70694] methodSignature4 0x600000263900
// 2018-01-29 22:28:11.793 IANLearn[3207:70694] methodSignature5 0x0

上面兩個(gè)方法都是從某個(gè)類中查找指定SEL的方法簽名拒炎,整個(gè)過程大概是是去對(duì)應(yīng)的類方法列表中尋找與該SEL匹配的方法Method挪拟,然后直接返回該Method的簽名,如果沒有查找到該方法則返回nil

有一點(diǎn)需要說明的是在其他語言里面方法簽名包含方法名稱和參數(shù)信息击你,而OC的NSMethodSignature 僅僅包含參數(shù)信息

NSInvocation是OC的方法調(diào)用器

OC語言中有多種方式去調(diào)用方法玉组,拋開c/c++的調(diào)用方式可以列舉出如下幾種:

  1. 對(duì)象/類對(duì)象直接調(diào)用方法[obj method:param1:],這種方式是最面向?qū)ο蟮淖藙?shì)了丁侄;
  2. [peformSelector withObject:]惯雳,這種方式它可以很方便的去延遲調(diào)用,或者丟到后臺(tái)線程調(diào)用鸿摇,并且調(diào)用沒有定義的SEL也不會(huì)導(dǎo)致編譯錯(cuò)誤石景,但它最大的問題是最多傳遞2個(gè)參數(shù),沒有返回值拙吉,并且參數(shù)只能是對(duì)象類型的所以不是很靈活潮孽;
  3. block調(diào)用 block(name, age) ,閉包可以看做是匿名方法筷黔,它的調(diào)用不用去對(duì)象類中去尋找方法恩商,本身block的結(jié)構(gòu)中就包含方法的實(shí)現(xiàn),它其中的方法不屬于某個(gè)對(duì)象只屬于閉包本身必逆;
    4.invocation調(diào)用怠堪,invocation調(diào)用是OC中最靈活的方法調(diào)用方式揽乱,它可以調(diào)用對(duì)象的私有方法,可以傳遞任意多個(gè)參數(shù)粟矿,可以兼容各種參數(shù)類型凰棉,并且可以存儲(chǔ)方法調(diào)用的返回值。
  • invocation的構(gòu)建
+ (NSInvocation *)invocationWithMethodSignature:(NSMethodSignature *)sig;

一個(gè)NSInvocation主要由方法簽名+參數(shù)值來確定, 通過方法簽名創(chuàng)建NSInvocation后我們給他的參數(shù)值就行陌粹,其中調(diào)用對(duì)象是第一個(gè)參數(shù)撒犀,方法名稱SEL是第二個(gè)參數(shù)

  • invocation的調(diào)用
- (void)invoke;
- (void)invokeWithTarget:(id)target;

NSInvocation的調(diào)用有2個(gè)方法,target參數(shù)可以直接設(shè)置target屬性或者設(shè)置為第一個(gè)參數(shù)掏秩,如果不設(shè)置則調(diào)用第二個(gè)方法將target傳入

// 測(cè)試
// 構(gòu)建對(duì)象 測(cè)試UILabel的setText:方法
UILabel *myObj = [UILabel new];
NSLog(@"invocation執(zhí)行前myObj.text=%@", myObj.text);
// 構(gòu)建方法簽名返回類型void編碼為v或舞,對(duì)象UILabel類型編碼為@,SEL編碼為:蒙幻,參數(shù)類型NSString編碼為@
NSMethodSignature *myMethodSignature = [NSMethodSignature signatureWithObjCTypes:"v@:@"];
// 構(gòu)建NSInvocation
NSInvocation *myInvocation = [NSInvocation invocationWithMethodSignature:myMethodSignature];
// 設(shè)置第1個(gè)參數(shù)
myInvocation.target = myObj;
// 設(shè)置第2個(gè)參數(shù)
myInvocation.selector = @selector(setText:);
// 設(shè)置第3個(gè)參數(shù)
NSString *newText = @"change new text";
[myInvocation setArgument:&newText atIndex:2];
[myInvocation retainArguments];
// 執(zhí)行
[myInvocation invoke];
NSLog(@"invocation執(zhí)行后myObj.text=%@", myObj.text);
  • invocation的使用場(chǎng)景
  1. 調(diào)用多參數(shù)的私有方法
    私有方法我們不能通過對(duì)象直接調(diào)用映凳,我們可以使用peformSelector的方式調(diào)用,但是對(duì)于多余2個(gè)參數(shù)的情況我們就沒辦法調(diào)用了邮破,這時(shí)候就可以構(gòu)建NSInvocation來調(diào)用了诈豌。
  2. 方法轉(zhuǎn)發(fā)調(diào)用
    方法轉(zhuǎn)發(fā)的forwardInvocation中需要我們?nèi)?zhí)行NSInvocation,通常是將NSInvocation通過更改target參數(shù)的形式轉(zhuǎn)發(fā)給其他對(duì)象來執(zhí)行抒和。

參考資料:https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html#//apple_ref/doc/uid/TP40008048-CH100-SW1

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末矫渔,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子摧莽,更是在濱河造成了極大的恐慌庙洼,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件镊辕,死亡現(xiàn)場(chǎng)離奇詭異送膳,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)丑蛤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來撕阎,“玉大人受裹,你說我怎么就攤上這事÷彩” “怎么了棉饶?”我有些...
    開封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)镇匀。 經(jīng)常有香客問我照藻,道長(zhǎng),這世上最難降的妖魔是什么汗侵? 我笑而不...
    開封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任幸缕,我火速辦了婚禮群发,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘发乔。我一直安慰自己熟妓,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開白布栏尚。 她就那樣靜靜地躺著起愈,像睡著了一般。 火紅的嫁衣襯著肌膚如雪译仗。 梳的紋絲不亂的頭發(fā)上抬虽,一...
    開封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音纵菌,去河邊找鬼阐污。 笑死,一個(gè)胖子當(dāng)著我的面吹牛产艾,可吹牛的內(nèi)容都是我干的疤剑。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼闷堡,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼隘膘!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起杠览,我...
    開封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤弯菊,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后踱阿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體管钳,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年软舌,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了才漆。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡佛点,死狀恐怖醇滥,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情超营,我是刑警寧澤鸳玩,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站演闭,受9級(jí)特大地震影響不跟,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜米碰,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一窝革、第九天 我趴在偏房一處隱蔽的房頂上張望购城。 院中可真熱鬧,春花似錦聊闯、人聲如沸工猜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)篷帅。三九已至,卻和暖如春拴泌,著一層夾襖步出監(jiān)牢的瞬間魏身,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來泰國(guó)打工蚪腐, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留箭昵,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓回季,卻偏偏與公主長(zhǎng)得像家制,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子泡一,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

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

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉颤殴,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,682評(píng)論 0 9
  • 參考鏈接: http://www.cnblogs.com/ioshe/p/5489086.html 簡(jiǎn)介 Runt...
    樂樂的簡(jiǎn)書閱讀 2,131評(píng)論 0 9
  • 轉(zhuǎn)載:http://yulingtianxia.com/blog/2014/11/05/objective-c-r...
    F麥子閱讀 728評(píng)論 0 2
  • 繼上Runtime梳理(四) 通過前面的學(xué)習(xí),我們了解到Objective-C的動(dòng)態(tài)特性:Objective-C不...
    小名一峰閱讀 741評(píng)論 0 3
  • 一鼻忠、Runtime簡(jiǎn)介 Runtime簡(jiǎn)稱運(yùn)行時(shí)涵但。OC就是運(yùn)行時(shí)機(jī)制,也就是在運(yùn)行時(shí)候的一些機(jī)制帖蔓,其中最主要的是消...
    林安530閱讀 1,060評(píng)論 0 2