- 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)用方式可以列舉出如下幾種:
- 對(duì)象/類對(duì)象直接調(diào)用方法
[obj method:param1:]
,這種方式是最面向?qū)ο蟮淖藙?shì)了丁侄; -
[peformSelector withObject:]
惯雳,這種方式它可以很方便的去延遲調(diào)用,或者丟到后臺(tái)線程調(diào)用鸿摇,并且調(diào)用沒有定義的SEL也不會(huì)導(dǎo)致編譯錯(cuò)誤石景,但它最大的問題是最多傳遞2個(gè)參數(shù),沒有返回值拙吉,并且參數(shù)只能是對(duì)象類型的所以不是很靈活潮孽; - 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)景
- 調(diào)用多參數(shù)的私有方法
私有方法我們不能通過對(duì)象直接調(diào)用映凳,我們可以使用peformSelector
的方式調(diào)用,但是對(duì)于多余2個(gè)參數(shù)的情況我們就沒辦法調(diào)用了邮破,這時(shí)候就可以構(gòu)建NSInvocation
來調(diào)用了诈豌。 - 方法轉(zhuǎn)發(fā)調(diào)用
方法轉(zhuǎn)發(fā)的forwardInvocation
中需要我們?nèi)?zhí)行NSInvocation
,通常是將NSInvocation
通過更改target
參數(shù)的形式轉(zhuǎn)發(fā)給其他對(duì)象來執(zhí)行抒和。