闡述#
相信iOS開發(fā)的同學如果使用了AFNetworking這個第三方框架,可能會碰到以下Bug:
Error Domain=com.alamofire.error.serialization.responseCode=-1016"Request failed:
unacceptable content-type: text/html"UserInfo={com.alamofire.serialization.response.error.response
= { URL: http://c.m.163.com/nc/article/headline/T1348647853363/0-140.html }
{ statuscode:200, headers { .....}......22222c22626f6172646964223a226e6577735f7368656
87569375f626273222c227074696d65223a22323031362d30332d30332031313a30323a3435227d5d7d>,
NSLocalizedDescription=Request failed: unacceptablecontent-type: text/html}
眾所周知,這是AFNetworking不支持解析text/html(非官方的html)格式的數(shù)據(jù).
思路#
按道理講:我們只要把
self.acceptableContentTypes= [NSSetsetWithObjects:@"application/json",@"text/json",
@"text/javascript",nil];
變成
self.acceptableContentTypes= [NSSetsetWithObjects:@"application/json",@"text/json",@"text/javascript",@"text/html",nil];
思路是完全正確的坚弱,但是我們真正處理起來就會有麻煩:
我們一般是用cocoapods來管理第三方庫的,方便第三方庫的版本更新。既然是pod管理的,我們就無法去更改源代碼,即便更改了,下次pod更新了,也會把修改好的代碼覆蓋掉枉长。這樣我們就需要其他方法來徹底解決這個問題了饺谬。
@implementation AFJSONResponseSerializer
- (instancetype)init {
self= [super init];
if(!self) {
return nil;
}
self.acceptableContentTypes= [NSSet setWithObjects:@"application/json",@"text/json",@"text/javascript",nil];
return self;
}
上面這個方法是在AFJSONResponseSerializer 的init方法了設置acceptableContentTypes冶匹。如果我們能夠復寫AFJSONResponseSerializer的init方法或者替換掉這個方法,改成我們想實現(xiàn)的,應該就可以實現(xiàn)了!
首先,大家想到的肯定是通過子類繼承的方法去復寫此方法,但是繼承肯定不方便了。
那么器予,通過類目去復寫此方法,能不能實現(xiàn)呢挪丢?答案是否定的
1.類目中不能super
2.類目的本質(zhì)是給原始類增加方法,而不是修改修改原始類的方法.要覆蓋原始類的方法可以通過子類繼承的方式,這里用繼承肯定不方便了狈谊。
重要的事情說三遍:
不要類目在覆蓋原始類的方法
不要類目在覆蓋原始類的方法
不要類目在覆蓋原始類的方法
原因是:縱觀蘋果的api,以NSString為例,有NSStringExtensionMethods,NSStringEncodingDetection等各種分類方法,如果我們再寫一個分類覆蓋原分類里的方法,那么系統(tǒng)就無法區(qū)分是A分類方法覆蓋B分類的方法,還是B分類方法覆蓋A分類的方法喜命。
最后,只有最后一個辦法了,通過類目去替換掉AFJSONResponseSerializer的init方法.
原理就是Method Swizzle
解決方案#
代碼實現(xiàn)如下:
#import <AFNetworking/AFNetworking.h>
@interface AFJSONResponseSerializer (Category)
@end
#import" AFJSONResponseSerializer+Category.h"
@implementation AFJSONResponseSerializer (Category)
+ (void)load {
staticdispatch_once_tonceToken;
dispatch_once(&onceToken, ^{
Method orignalMethod =class_getClassMethod([selfclass],@selector(init));
Method swizzledMethod
=class_getClassMethod([selfclass],@selector(dev4mobile_init));
method_exchangeImplementations(orignalMethod, swizzledMethod);
});
}
- (instancetype)dev4mobile_init {
[self dev4mobile_init];
self.acceptableContentTypes = [NSSetsetWithObjects:@"application/json",@"text/json",@"text/javascript",@"text/html",nil];
return self;
}
@end
此處要注意的有幾點:
1.load是在程序的main函數(shù)之前就調(diào)用的,當程序開始運行,而不是編譯的時候,調(diào)用load方法。未了保證全局的去交換AFJSONResponseSerializer的init方法和dev4mobile_init的IMP(函數(shù)指針)河劝。
2.用dispatch_once去保證兩個方法的指針只交換一次壁榕。為了避免多線程出現(xiàn)多次調(diào)用的結果。
3.有的人可能覺得調(diào)用[self dev4mobile_init];方法時會產(chǎn)生遞歸赎瞎。其實不然牌里,正確的順序是這樣的,AFJSONResponseSerializer先調(diào)用自身init方法,但是指向init方法的selector已經(jīng)指向了dev4mobile_init方法了,所以會調(diào)到分類方法中,而調(diào)用[self dev4mobile_init];方法是,指向dev4mobile_init的selector指向的是AFJSONResponseSerializer的init方法,走完init方法,就會走
self.acceptableContentTypes= [NSSetsetWithObjects:@"application/json",@"text/json",@"text/javascript",@"text/html",nil];
這行代碼了务甥。
4.Method swizzledMethod原理就是交換兩個selector所指向的IMP.還有很多其他的實際用途:例如你不想調(diào)用UITextField一個的代理方法,想調(diào)到自定義的方法,此時就可以用她來hook一下牡辽。
5.把dev4mobile_init換成xxx_init(xxx為自己命名的前綴)。
這里是關于Method Swizzle的幾個陷阱:
Method swizzling is not atomic
Changes behavior of un-owned
codePossible naming conflicts
Swizzling changes the method's arguments
The order of swizzles matters
Difficult to understand (looks recursive)
Difficult to debug
大家自己體會一下敞临。
推薦念茜的一篇Method Swizzle博客給大家http://blog.csdn.net/yiyaaixuexi/article/details/9374411