iOS Swizzle的正確使用方式(原文翻譯)

2021-08-25 19:29:48 字數 4231 閱讀 7226

文章翻譯自

感謝作者bryce buchanan

通常在執行時,swizzle是通過用乙個方法的實現來替換另乙個方法的實現來運作的。運用swizzle可能是因為不同的需求:重寫預設方法,甚至是動態的方法載入。我曾經看到很多發出來的部落格上討論swizzle,他們很多都提供了一些相當不好的用法。這些用法在你獨自寫專案的時候用起來無傷大雅,但是如果你在為乙個第三方開發者提供framework的時候,swizzle可能會讓本應該執行順利的部分出現混亂。所以,在oc中怎樣才是swizzle的正確使用方式呢?

讓我們從基礎說起,當我說起swilling的時候通常是用我自定義的方法來替代原有的方法,然後在自定義的方法裡呼叫原有的方法。oc在runtime裡是允許這樣操作的。在執行時,oc的方法methods是以c語言的結構體形式出現的,乙個被定義為struct objc_method的結構體:

struct objc_method
method_name就是當前呼叫方法的selector對應的名字,*method_types是c編碼的字串型別的引數和返回值,method_imp

是當前函式的指標(我們待會會討論更對關於imp的問題)。

你可以用下面的方法拿到這個物件(在oc執行時有很多拿到他們的渠道):

method class_getclassmethod(class aclass,sel aselector);

method class_getinstancemethod(class aclass,sel aselector);

拿到method就可以拿到mehod內部的結構體從而改變他們內部的實現。method_imp是imp型別,定義為id(*imp)(id,sel,...),也是乙個帶有指標、selector和一串作為引數的帶有編號變數的函式。用impmethod_setimplemention(method method,imp imp)可以改變method_imp,引數imp是method結構體裡面的,是方法的實現,method是你想要改變的方法,然後再返回跟method對應的原生imp,這是swizzle的正確用法。

下面是swizzle的常用用法,當直接用乙個方法來代替另乙個方法實現的時候,會帶來一些不易察覺的影響。

void method_exchangeimplementation(method m1,mnethod m2)
為了弄清楚這些影響,讓我們來看一下m1和m2在被呼叫前後的結構。

method m1
method m2
以上是方法未呼叫之前的結構,oc**編譯這些結構就是這樣:

@implementation myclass

- (void)originalmethodname //m1

- (void)swizzle_originalmethodname //m2

@end

然後我們呼叫:

m1 = class_getinstancemethod([myclass class],@selector(originalmethodname))

m2 = class_getinstancemethod([myclass class],@selector(swizzle_originalmethodname))

method_exchangeimplementation(m1,m2)

現在方法看起來將會是這樣:

method m1
method m2
兩個方法的imp位址交換了一下,也就是說只改變了imp。注意到如果我們想要執行原生方法我們得呼叫[self swizzle_originalmethodname],如果原生方法依賴於_cmd作為方法名,這將導致傳給原生方法的_cmd的值變成@selector(swizzle_originalmethodname)。這種swizzle的方式(下面有例子)已經對正常的函式編碼帶來了混亂,是應該避免的。

- (void)originalmethodname  //m1

現在我們看一下用method_exchangedimplementations()函式進行swizzle的正確用法。

用c函式定義乙個imp方法來代替新建立的oc函式-(void)swizzle_originalmethodname

void _swizzle_originalmethodname(id self,sel _cmd)

我們可以把這個c函式轉換成乙個imp:

imp swizzleimp = (imp)_swizzle_originalmethodname;
然後可以把swizzleimp傳給method_setimplementation( ):

method_setimplemention(method ,swizzleimp);
上面這個方法返回的是原生的imp:

imp originalimp = method_setimplementation(method,swizzleimp);
現在,originalimp可以用來呼叫原生方法了:

originalimp(self,_cmd);
這裡有乙個例子:

@inte***ce swizzleexampleclass : nsobject

- (void) swizzleexample;

- (int) originalmethod;

@end

static imp __original_method_imp;

int _replacement_method(id self, sel _cmd)

@implementation swizzleexampleclass

- (void) swizzleexample //call me to swizzle

- (int) originalmethod

測試一下就能看出來:

swizzleexampleclass* example = [[swizzleexampleclass alloc] init];

int originalreturn = [example originalmethod];

[example swizzleexample];

int swizzledreturn = [example originalmethod];

assert(originalreturn == 1); //true

assert(swizzledreturn == 2); //true

總而言之,為了避免與其他第三方sdk造成混亂,不要用oc的方法和method_swapimplementations()來進行swizzle,而是把imp轉換成c函式。這將避免oc自身的方法帶來的額外煩惱的資訊(可以理解為oc的語言特性帶來的問題:譯者注),比如新的方法名。如果你想用swizzle,最好的結果就是不留下痕跡。

不要忘記,所有的oc方法傳遞了兩個隱藏的引數:對self的引用(id self),和方法名selector(sel _cmd)。

如果imp的呼叫返回值為空void,那你不得不注意了。因為arc假定所有的imp返回乙個id,將會嘗試引用乙個空的和原始型別

imp animp; //represents objective-c function

// -uiviewcontroller viewdidload;

((void(*)(id,sel))animp)(self,_cmd); //call with a cast to prevent

// arc from retaining void.

的正確使用 眼霜正確的使用方法

眼睛周圍的肌膚是乙個人整張臉上最脆弱的部分,它嬌嫩柔弱,只要有稍稍的護理不慎,就會帶來不堪設想的後果,黑眼圈 眼袋之類的問題更是家常便飯了。我們可以使用日常眼霜保護眼周肌膚,緩解眼周肌膚的問題。然而,對於日常眼霜的使用,你知道多少,有一些你所不知道的錯誤用法,你是不是也中招了 日常眼霜的作用 日常眼...

stl erase 的正確使用

stl erase 正確使用 stl之map erase方法的正確使用 stl的map表裡有乙個erase方法用來從乙個map中刪除掉指令的節點 eg mapmaptest typedef map iterator iter iter iter maptest.find key maptest.er...

typename的正確使用

typename 的正確使用 一 在宣告模板引數時,class 和typename 是可互換的。以下完全等價 templateclass widget templateclass widget 二 通常情況下,必須用 typename 去標識巢狀依賴型別名,但在基類列表中或在乙個 建構函式的成員初始...