iOS - method 底層詳解

[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
bnum 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];
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市霜医,隨后出現(xiàn)的幾起案子齿拂,更是在濱河造成了極大的恐慌,老刑警劉巖肴敛,帶你破解...
    沈念sama閱讀 221,820評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件署海,死亡現(xiàn)場離奇詭異,居然都是意外死亡医男,警方通過查閱死者的電腦和手機砸狞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來镀梭,“玉大人刀森,你說我怎么就攤上這事》崂保” “怎么了?”我有些...
    開封第一講書人閱讀 168,324評論 0 360
  • 文/不壞的土叔 我叫張陵禽捆,是天一觀的道長笙什。 經(jīng)常有香客問我,道長胚想,這世上最難降的妖魔是什么琐凭? 我笑而不...
    開封第一講書人閱讀 59,714評論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮浊服,結(jié)果婚禮上统屈,老公的妹妹穿的比我還像新娘。我一直安慰自己牙躺,他們只是感情好愁憔,可當我...
    茶點故事閱讀 68,724評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著孽拷,像睡著了一般吨掌。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上脓恕,一...
    開封第一講書人閱讀 52,328評論 1 310
  • 那天膜宋,我揣著相機與錄音,去河邊找鬼炼幔。 笑死秋茫,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的乃秀。 我是一名探鬼主播肛著,決...
    沈念sama閱讀 40,897評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼圆兵,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了策泣?” 一聲冷哼從身側(cè)響起衙傀,我...
    開封第一講書人閱讀 39,804評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎萨咕,沒想到半個月后统抬,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,345評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡危队,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,431評論 3 340
  • 正文 我和宋清朗相戀三年聪建,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片茫陆。...
    茶點故事閱讀 40,561評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡金麸,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出簿盅,到底是詐尸還是另有隱情挥下,我是刑警寧澤,帶...
    沈念sama閱讀 36,238評論 5 350
  • 正文 年R本政府宣布桨醋,位于F島的核電站棚瘟,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏喜最。R本人自食惡果不足惜偎蘸,卻給世界環(huán)境...
    茶點故事閱讀 41,928評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望瞬内。 院中可真熱鬧迷雪,春花似錦、人聲如沸虫蝶。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽能真。三九已至慧邮,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間舟陆,已是汗流浹背误澳。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留秦躯,地道東北人忆谓。 一個月前我還...
    沈念sama閱讀 48,983評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像踱承,于是被迫代替她去往敵國和親倡缠。 傳聞我的和親對象是個殘疾皇子哨免,可洞房花燭夜當晚...
    茶點故事閱讀 45,573評論 2 359

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