C語言是靜態語言,它的工作方式是通過函數調用,這樣在編譯時我們就已經確定程序如何運行的。而Objective-C是動態語言,它並非通過調用類的方法來執行功能,而是給對象發送消息,對象在接收到消息之後會去找匹配的方法來運行。這種做法就把C語言在編譯時的工作挪到了運行時來做,可以獲得額外的靈活性。
在Objective-C中有個@selector,在很多地方被翻譯成“選擇子”。實際上,對於類的實例對象來說,類的方法是用一個數字來代表的,並非是我們看到的一個長長的帶著:這個字符的一串字符串。通過這個@selector就可以把這個方法的名字轉成所對應的數字。當一個類確定後,實際上每個方法的@selector的值就是固定的,說到這裡,你一定可以想到method swizzling是什麼一個東東了,沒錯,如果我們原來有個A方法,@selector(A)就是一個數字,我們的對象在接收到一個消息後就去查找對應的方法並運行——如果,我們把@selector(B)的數字換成了原來@selector(A)的數字,那麼此時對象雖然受到A消息,但會去運行B方法!
在iOS中,這是完全可以實現的,那麼我們什麼時候會需要這麼做呢?我覺得有2個時候:
1. 破解,毋庸諱言,這絕對是破解的利器,不解釋了。
2. 在開發調試過程中,如果你對某個庫裡的方法不確定或者覺得需要擴展的時候,你可以自己寫一個去代替它。因為Objective-C是有Category的,所以擴展功能沒啥必要,但調試時增加一些打印語句是很方便實際的。
舉個例子,NSString裡面的lowercaseString方法,如果我不太清楚這個方法都干了什麼,我就可以自己寫個方法來替換它,這個方法裡面增加打印語句,這樣log裡面就一目了然了。
首先需要增加一個NSString的Category
@interface NSString (wzTest) - (NSString*)myLowerString; @end @implementation NSString (wzTest) - (NSString*)myLowerString { NSString *lowerString = [self myLowerString]; NSLog(@"%@ => %@", self, lowerString); return lowerString; } @end這裡有一個地方解釋一下,在myLowerString方法裡面,看起來遞歸調用了自身。但是,我們會用原來的lowercaseString方法去替換自己寫的myLowerString方法,所以這裡並沒有調用自身,而是調用了原來的lowercaseString方法。這點請注意一下。
其次替換系統原來的lowercaseString方法,使用runtime裡面的方法。
Method originalMethod = class_getInstanceMethod([NSString class], @selector(lowercaseString)); Method swapMethod = class_getInstanceMethod([NSString class], @selector(myLowerString)); method_exchangeImplementations(originalMethod, swapMethod); NSString *testStr = @"thIs is THE Test STRING"; NSLog(@"lowerString of testStr=%@", [testStr lowercaseString]);
2014-05-29 22:17:55.514 testTableView[1582:a0b] thIs is THE Test STRING => this is the test string
2014-05-29 22:17:55.514 testTableView[1582:a0b] lowerString of testStr=this is the test string
我們可以看到,系統中使用是繼續使用lowercaseString方法的,不過實際執行的是我們新增的方法。當你不需要這樣做的時候,關閉method swizzling方法就可以恢復了。
我們的例子中是增加了打印語句,實際上還可以做更多地操作。這在用第三方庫調試的時候是非常有用的一個方法,可以很方便的查看變量的內容或做一些其他工作。調試結束後,關閉method swizzling就可以正常的工作。