起因
最近在複習iOS中的訊息轉發機制,如果需要在動態方法解析這一階段對訊息進行處理,一般需要呼叫class_addMethod方法給類動態地增加方法,例如:
我當時敲程式碼的時候發現自己對於class_addMethod這個方法的第四個引數const char *types不太清楚,後來搜了一下型別編碼,發現網上的文章主要是對於屬性的型別編碼進行了介紹,也沒有人對函式的型別編碼進行介紹,就去看了一下文件瞭解了一下。再後來發現介紹訊息轉發機制的那篇文章中對於這個引數的傳入是錯誤的,
圖中新增的方法是void functionForMethod1(idself, SEL _cmd),
傳入的函式型別編碼是”@:”,
而正確的函式型別編碼應該是”v@:”,
探究
下面讓我們來看看這個方法:
BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types);
這個方法主要接受四個引數
Class cls 要新增方法的類
SEL name 被新增方法的名字
IMP imp 新增的方法的實現
const char *types 描述方法引數型別的字元陣列。
描述方法引數型別的字元陣列的第一個字元是代表返回值的型別,後面的字元依次代表引數的型別,因為Objective-C中的函式會包含兩個隱式引數,也就是方法呼叫者和方法名,例如
+(void)method
實際應該是
void method(id self, SEL _cmd)
如果返回值為空,那麼函式的型別編碼的第一個字元是v,如果不為空,則為返回值型別對應的編碼,詳細的可以看下面的編碼對應表
因為第一個引數是方法呼叫者,它的型別肯定是物件型別,所以型別編碼的第二個字元一定是@
因為第二個引數是方法名的型別,第三個字元一定是:
所以這個函式
void method(id self, SEL _cmd)
的型別編碼為 “v@:”
那麼如果要新增的函式是一個set函式,型別編碼是怎麼樣的呢?
-(void)setA:(NSString *)a
同理,set方法實際的函式是這樣的:
void setA(id self, SEL _cmd, id a)
與上面無引數的方法相比,只是多了一個引數,
所以型別編碼為”v@:@”,程式碼為
class_addMethod(self, @selector(setA:), (IMP)setA,”v@:@”);
悔悟。。。
最後發現自己還是太年輕了。。。
蘋果還是考慮到了開發者手動寫函式的型別編碼容易出錯的情況,所以還是提供了api來獲取函式的型別編碼(其實我想說。。。既然能提供api獲取函式的型別編碼,為什麼不在class_addMethod的實現中去獲取函式的型別編碼,而需要外部傳入)
主要是有這麼一個函式
const char * _Nullablemethod_getTypeEncoding(Method _Nonnull m)
通過傳入類名和,獲函式名,獲取函式的型別編碼,例如這樣: