AOP | 由一个奇葩需求衍生的Method Swizzling实际使用案例

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

整个交换就这样完成了,以后要修改或调整可以直接在这个方法里做统一处理。

几个注意点

  1. 在类方法里,self就是指这个class
  2. 写在+ (void)load方法里是为了保证方法调用前交换已经完成;
  3. 加上dispatch_once的原因是规避有人手动调load方法以及其它多次调用的极端情况;
  4. 判断class_addMethod是为了保证即便originalSelector只在父类中实现,也能达到方法交换的目的。

有些虽然看似多余,但实际上是为了规范。按照规范来写就是了,这样可以尽可能的避免掉坑。

还没完,最重要的来了

每当你在项目中使用一次Method Swizzling,不管运用得多么精髓,都相当于埋了颗地雷,为了避免将来有人(接手项目的人或者健忘的你)不小心踩雷,需要将地雷的位置标出来。

因此你需要在你项目的readme文件中,把使用到Method Swizzling的地方写清楚,使用的原因也写清楚,如:

AOP | 由一个奇葩需求衍生的Method Swizzling实际使用案例_第1张图片

只要这个接手项目的人有看readme的意识,了解Method Swizzling的基本知识,我相信他就不会被坑。

你可能感兴趣的:(AOP | 由一个奇葩需求衍生的Method Swizzling实际使用案例)