Method Swizzling相信大家都不陌生,也相信大家在实际开发中很少用到这个东西,曾经有人称它为iOS界的毒瘤,或许这种说法略显夸张,但如果滥用,那就真的是毒瘤中的毒瘤了。
背景
我:“怎么图片全部没有显示了?”
后台那边表示:为了将来方便换服务器,决定将图片url的拼接任务交给前端,前端先从一个接口获取拼接字段然后保存下来,之后拿到的图片的url,把前缀全部替换为这个字段。
不知道你们听到这里有没有说一句MMP。
反正我是长见识了。
然而两个安卓却表示:“嗯嗯嗯,是是是。”
我,一个外派的程序猿,也是此处唯一一个iOS程序猿,没有iOS队友,一直视为队友的两个安卓开发还没开打就投降了,我还有反抗的余地?
被盘已成定局,只有好好想想接下来的路。
分析
1.现状:
项目中涉及到网络图片的控件只有一个:UIImageView
,使用的三方是SDWebImage的UIImageView+WebCache
,用到的方法有两个:
- (void)sd_setImageWithURL:(nullable NSURL *)url;
和
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder;
这两个方法贯穿整个项目。
2.应对措施
写个工具方法,将后台返回的URL转化为前端需要的URL。虽然可行,但是每个涉及到网络图片的地方都要调用一下工具方法真的很繁琐。
重写SDWebImage的相关方法虽然可行,但是这个库我是用CocoaPods管理的,如果修改了源码,以后更新库都会畏手畏脚。并且直接对核心三方的源码进行修改这本身也是我不能接受的;
其实我一开始想到的就是使用Method Swizzling将相关方法实现替换成自己的。
虽然我对Method Swizzling一直持保守态度,但是此处我还是觉得Method Swizzling是最佳选择。
具体实现
因为- (void)sd_setImageWithURL:(nullable NSURL *)url
和- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder
这两个方法都是调用的同一个方法:
- (void)sd_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock;
所以只需要交换这个公共方法即可:
@implementation UIImageView (ImageUrlAdjust)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
SEL SD_SEL = @selector(sd_setImageWithURL:placeholderImage:options:progress:completed:);
SEL CQ_SEL = @selector(cq_setImageWithURL:placeholderImage:options:progress:completed:);
Method SD_METHOD = class_getInstanceMethod(self, SD_SEL);
Method CQ_METHOD = class_getInstanceMethod(self, CQ_SEL);
BOOL success = class_addMethod(self, SD_SEL, method_getImplementation(CQ_METHOD), method_getTypeEncoding(CQ_METHOD));
if (success) {
class_replaceMethod(self, SD_SEL, method_getImplementation(SD_METHOD), method_getTypeEncoding(SD_METHOD));
} else {
method_exchangeImplementations(SD_METHOD, CQ_METHOD);
}
});
}
- (void)cq_setImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock {
// 拿到url
NSString *originUrlString = url.absoluteString;
// 修改
NSString *currentUrlString = [@"呵呵哒" stringByAppendingPathComponent:[originUrlString componentsSeparatedByString:@"啪啪啪:"].lastObject];
// 再调用
[self cq_setImageWithURL:[NSURL URLWithString:currentUrlString] placeholderImage:placeholder options:options progress:progressBlock completed:completedBlock];
}
@end
整个交换就这样完成了,以后要修改或调整可以直接在这个方法里做统一处理。
几个注意点
- 在类方法里,
self
就是指这个class
; - 写在
+ (void)load
方法里是为了保证方法调用前交换已经完成; - 加上
dispatch_once
的原因是规避有人手动调load
方法以及其它多次调用的极端情况; - 判断
class_addMethod
是为了保证即便originalSelector只在父类中实现,也能达到方法交换的目的。
有些虽然看似多余,但实际上是为了规范。按照规范来写就是了,这样可以尽可能的避免掉坑。
还没完,最重要的来了
每当你在项目中使用一次Method Swizzling,不管运用得多么精髓,都相当于埋了颗地雷,为了避免将来有人(接手项目的人或者健忘的你)不小心踩雷,需要将地雷的位置标出来。
因此你需要在你项目的readme文件中,把使用到Method Swizzling的地方写清楚,使用的原因也写清楚,如:
只要这个接手项目的人有看readme的意识,了解Method Swizzling的基本知识,我相信他就不会被坑。