[toc]
參考
method
Method
typedef struct objc_method *Method;
objc_method
objc_method 和 method_t 是等價的
struct objc_method {
SEL method_name;
char *method_types;
IMP method_imp;
};
method_t
method_t 是對 方法 / 函數(shù) 的封裝, 是蘋果內(nèi)部使用的, 沒有公開的類型
方法 / 函數(shù) 的底層結(jié)構(gòu)就是 method_t
struct method_t {
SEL name; // 函數(shù)名
const char *types; // 編碼 (返回值類型、參數(shù)類型)
MethodListIMP imp; // 指向函數(shù)的指針 (函數(shù)地址)
struct SortBySELAddress :
public std::binary_function<const method_t&, const method_t&, bool>
{
bool operator() (const method_t& lhs,
const method_t& rhs)
{ return lhs.name < rhs.name; }
};
};
using MethodListIMP = IMP;
IMP
指向函數(shù)的指針 (函數(shù)地址)
代表函數(shù)的具體實現(xiàn)
// Objc.h 中 IMP 的定義:
/// A pointer to the fu nction of a method implementation.
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ );
#else
typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, …); // 相當于給id (*)(id, SEL, ...)) 取別名IMP
#endif
任何繼承于 NSObject 的類都能自動獲得runtime的支持屉栓。在類中有一個isa指針, 指向該類定義的成員組成的結(jié)構(gòu)體, 這個結(jié)構(gòu)體是由編譯器編譯時為類創(chuàng)建的乔遮。這個結(jié)構(gòu)體包含一個指向父類的指針和一個hash表, 即 Dispatch table(分發(fā)表), 是一張SEL和IMP的對應關(guān)系表箕宙。值是函數(shù)指針I(yè)MP, SEL就是查表時所用的鍵重贺。
SEL 和 IMP 是映射關(guān)系, Method Swizzling 可以改變這個映射關(guān)系;
SEL的存在只是為了加快方法的查詢速度, 省去了字符串(即方法名)比對的時間, 可以直接用SEL生成的KEY去查找, 就像我們直接根據(jù)NSDictionary的Key去讀取相應的Value一樣。
為什么不直接獲得函數(shù)指針I(yè)MP, 而要從SEL這個編號走一圈再回到函數(shù)指針呢牲证?
我們可以讓一個SEL指向不同的函數(shù)指針I(yè)MP, 這樣就可以完成一個方法名在不同時候執(zhí)行不同的函數(shù)體军洼。另外可以將SEL作為參數(shù)傳遞給不同的類執(zhí)行。也就是說我們某些業(yè)務我們只知道方法名但需要根據(jù)不同的情況讓不同類執(zhí)行的時候甚淡,SEL可以幫助我們大诸。
SEL
SEL 代表方法\函數(shù)名捅厂,一般叫做選擇器,底層結(jié)構(gòu)跟 char *
類似
typedef struct objc_selector *SEL;
可以通過 @selector()
和 sel_registerName()
獲得
SEL selA = @selector(setString:);
SEL selB = sel_registerName("setString:");
在類加載的時候, 編譯器會生成與方法相對應的選擇子, 并注冊到 Objective-C 的 Runtime 運行系統(tǒng)资柔。
不同類中, 無論參數(shù)類型是否相同, 只要方法名相同, 所對應的SEL是相同的
所以在OC中, 同一個類(及類的繼承體系)中, 不能存在2個同名的方法, 即使參數(shù)類型不同也不行焙贷。
相同的方法只能對應一個SEL。這也就導致 Objective-C在處理相同方法名且參數(shù)個數(shù)相同但類型不同的方法方面的能力很差贿堰。
當然, 不同的類可以擁有相同的selector, 這個沒有問題辙芍。不同類的實例對象執(zhí)行相同的selector時,會在各自的方法列表中去根據(jù)selector去尋找自己對應的IMP羹与。
工程中的所有的SEL組成一個Set集合故硅,Set的特點就是唯一,因此SEL是唯一的纵搁。
因此吃衅,如果我們想到這個方法集合中查找某個方法時,只需要去找到這個方法對應的SEL就行了腾誉,SEL實際上就是根據(jù)方法名hash化了的一個字符串徘层,而對于字符串的比較僅僅需要比較他們的地址就可以了,可以說速度上無語倫比@啊趣效!但是,有一個問題猪贪,就是數(shù)量增多會增大hash沖突而導致的性能下降(或是沒有沖突跷敬,因為也可能用的是perfect hash)。但是不管使用什么樣的方法加速热押,如果能夠?qū)⒖偭繙p少(多個方法可能對應同一個SEL)干花,那將是最犀利的方法。那么楞黄,我們就不難理解池凄,為什么 SEL僅僅是函數(shù)名了。
_cmd
OC的編譯器在編譯后會在每個方法中加2個隱藏的參數(shù):
_cmd : SEL類型的指針, 代表當前方法的 selector鬼廓。
self : id類型的指針, 指向當前對象的指針肿仑。我們平時之所以能在OC函數(shù)中調(diào)用self, 正是因為函數(shù)中有隱藏起來了的self參數(shù)
types
types 包含了函數(shù)返回值類型、參數(shù)類型編碼的字符串
/*
"v16@0:8"
v 返回值 void
@ 參數(shù) (id)self
: 參數(shù) (SEL)_cmd
*/
- (void)test;
/*
"i24@0:8i16f20"
i 返回值 int
24 所有參數(shù)所占字節(jié)數(shù)
@ 參數(shù)1 (id)self
0 參數(shù)1 從第0個字節(jié)開始
: 參數(shù)2 (SEL)_cmd
8 參數(shù)2 從第8個字節(jié)開始
i 參數(shù)3 int
16 參數(shù)3 從第16個字節(jié)開始
f 參數(shù)4 float
20 參數(shù)4 從第20個字節(jié)開始
*/
- (int)test:(int)age height:(float)height;
@encode
@encode是編譯器指令之一碎税。
@encode返回一個給定的Objective-C 類型編碼(Objective-C Type Encodings)尤慰。
這是一種內(nèi)部表示的字符串,類似于 ANSI C 的 typeof 操作雷蹂。
蘋果的 Objective-C 運行時庫(runtime)內(nèi)部利用類型編碼幫助加快消息分發(fā)伟端。
NSLog(@"%s", @encode(id)); // @
NSLog(@"%s", @encode(SEL)); // :
type encodings
Code | Meaning |
---|---|
c |
A char
|
i |
An int
|
s |
A short
|
l |
A long``l is treated as a 32-bit quantity on 64-bit programs. |
q |
A long long
|
C |
An unsigned char
|
I |
An unsigned int
|
S |
An unsigned short
|
L |
An unsigned long
|
Q |
An unsigned long long
|
f |
A float
|
d |
A double
|
B |
A C++ bool or a C99 _Bool
|
v |
A void
|
* |
A character string (char * ) |
@ |
An object (whether statically typed or typed id ) |
# |
A class object (Class ) |
: |
A method selector (SEL ) |
[array type] | An array |
{name=type...} | A structure |
(name=type...) | A union |
b num |
A bit field of num bits |
^ type |
A pointer to type |
? |
An unknown type (among other things, this code is used for function pointers) |
APIs
/// NSObject.h
// If the receiver is an instance, aSelector should refer to an instance method;
// if the receiver is a class, it should refer to a class method.
- (IMP)methodForSelector:(SEL)aSelector;
// 向類請求實例方法的IMP。
+ (IMP)instanceMethodForSelector:(SEL)aSelector;
Method _Nullable class_getInstanceMethod(Class _Nullable cls, SEL _Nonnull name);
// 向runtime系統(tǒng)注冊一個方法名稱, 并生成SEL和方法名稱的映射, 最后返回SEL匪煌。如果該方法名稱已經(jīng)注冊, 則直接返回其對應的SEL责蝠。
SEL sel_registerName(const char *str);
SEL methodId = @selector(methodName); // OC編譯器的命令
SEL method_getName(Method m);
SEL NSSelectorFromString(NSString *aSelectorName); // NSObjCRuntime.h
NSString *NSStringFromSelector(SEL aSelector); // NSObjCRuntime.h
const char *sel_getName(SEL aSelector); // runtime.h
IMP _Nullable class_getMethodImplementation(Class _Nullable cls, SEL _Nonnull name);
IMP _Nonnull method_getImplementation(Method _Nonnull m);
IMP _Nonnull imp_implementationWithBlock(id _Nonnull block)
IMP _Nonnull method_setImplementation(Method _Nonnull m, IMP _Nonnull imp);
IMP _Nullable class_replaceMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types);
BOOL class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types);
示例
// 獲取當前方法的IMP, 可使用隱藏參數(shù)self和_cmd党巾。
IMP currentIMP = [[self class] instanceMethodForSelector:_cmd];