runtime学习之 - 黑魔法 Swizzling,改变系统方法!

来源:互联网 发布:网络自动发信息软件 编辑:程序博客网 时间:2024/06/09 14:56

上篇文章讲述了 runtime 中的关联 association(传送门),今天我们继续来学习 runtime,揭开它神秘的黑魔法-swizzling!


也许很多人都和我一样,不知道什么时候该用 runtime(有一次某 bat 公司的面试,就问我什么时候用到了 runtime)。那我们就先来举个用 runtime 的栗子

asociation 的栗子我们上篇文章已经举过了,就是在分类中添加属性的时候,用于 setter 方法和 getter 方法中。那么 swizzling 呢?


假设你现在开发了一个 app,它的首页是一个 tableView,当没有数据的时候你的首页会显示什么呢?

(有的同学可能会问:“为什么会没有数据呢?” 也许是网挂了,或者数据丢了,反正就是有这种可能啦。。。)

什么也不显示肯定不太好吧?那样用户岂不是会很困惑?用户不知道是什么情况导致了没有数据,也许用户会觉得你这个 app 本来就什么都没有呢

所以我们应该在没有数据的时候做一些处理,比如我们公司的项目,它的首页就是 tableView,在没有数据的时候,我们的做法是显示一张背景图片,并且用一个 label 显示 “网络不给力,请点击屏幕重试”,并且给 tableView 加入了一个点击事件,点击 tableView 就会重新请求数据。

每次用 tableView 都要判断是否为空并且做这些事岂不是很笨?所以我们直接写一个 tableView 的分类 UITableView+EmptyDataSet。


接下来我们来思考一个问题:应该什么时候去判断 tableView 是否为空呢?


答案是,在 tableView reloadData 之后。

(有的同学可能会说:“这不是废话吗!在 reloadData 之前判断有啥用,reloadData 之后也许就变了啊!“)

嗯的确是句废话,这句话就好比你正准备要吃饭,但还没开始吃的时候我就问你吃饱了吗,显然我应该等你吃完再问你。


重点其实是,怎么在 reloadData 之后判断请仔细思考这个问题


有的同学可能又会说了:“这还不简单,像下面这样就行了啊”

- (void)reloadData {    [self reloadData];    // 做你想做的事}

如果你也是这么想的,那么,请再一次仔细思考这个问题


很显然,这句代码是个死循环。也许有的同学会说:“重写 reloadData 方法”。

呵呵,你可真敢说!系统的方法你重写一个给我看看?Talk is cheap,show me the code!


那怎么办呢?究竟怎么才能在 reloadData 之后做我们想做的事呢?


=================== 我 == 是 == 分 == 割 == 线 ===================

ok,背景交代完毕,我们的主角 swizzling 终于要登场了,它就可以办到这件事情。


首先,把 reloadData 方法和我们写的某个方法交换。

method_exchangeImplementations(originalMethod, swizzledMethod);


其中 originalMethod 是源方法,也就是我们要交换的 reloadData。swizzledMethod 是目标方法,是我们自己写的一个方法,比如就叫做 swizzled_reloadData。(我们先简单地这样说,一会再具体地说)

然后,实现我们自己写的这个方法,搞定!

- (void)swizzled_reloadData {    [self swizzled_reloadData];    // 做你想做的事}

有的同学可能会说:“咦?这不还是死循环吗?!”

并不是。

- (void)swizzled_reloadData; 的确是我们自己写的方法,但是 [self swizzled_reloadData]; 调用的却不是这个方法,而是调用了系统中的 reloadData 方法,这是因为我们把 reloadData 的实现和 swizzled_reloadData 的实现交换了。请仔细思考这句话和下面这句话

现在你在任意一个地方调用 [self.tableView reloadData]; 其实调用的都不是系统中的 reloadData 方法,而是我们在 tableView 的分类中写的 swizzled_reloadData 方法,在 swizzled_reloadData 方法中调用的那句才是系统中的 reloadData 方法。


如果这几句你理解了,那我们就可以具体来说说了。交换两个方法的实现当然不是简单一句就搞定的啦。事实上是这样的:

Class class = [self class];        SEL originalSelector = @selector(reloadData);SEL swizzledSelector = @selector(swizzled_reloadData);        Method originalMethod = class_getInstanceMethod(class, originalSelector);Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);method_exchangeImplementations(originalMethod, swizzledMethod);

这些其实也比较好理解,就是获取源方法和目标方法的实现。因为我们要交换的是两个方法的实现。

其实在交换之前,我们应该先这样:

Class class = [self class];        SEL originalSelector = @selector(reloadData);SEL swizzledSelector = @selector(swizzled_reloadData);        Method originalMethod = class_getInstanceMethod(class, originalSelector);Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);        BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));if (didAddMethod) {    class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));} else {    method_exchangeImplementations(originalMethod, swizzledMethod);}

BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)

这个方法的意思是,往 cls 这个类中添加一个叫做 name 的方法,这个方法的具体实现是 imp。types 是编码类型,这里我们讨论的重点不是它,先不用太在意它。如果添加成功则返回 YES,否则返回 NO。


这句话的意思就是,我们先尝试往当前类(也就是 UITableView)中添加一个叫做 reloadData 的方法。为什么呢?因为当前类(主类)不一定实现了原方法(此处为reloadData方法),而有可能是继承了父类的方法。

当 class_addMethod 返回 NO 时,说明主类本身就实现了需要被替换的方法,这种情况比较简单,我们直接交换两个方法的实现就可以了。当 class_addMethod 返回 YES 时,说明主类本身没有实现需要被替换的方法,而是继承了父类的实现。这时 class_getInstanceMethod 函数获取到的 originalSelector 指向的就是父类的方法。然后我们再通过 class_replaceMethod 把父类的实现替换到我们自定义的 swizzled_reloadData 中,这样就达到了在 swizzled_reloadData 方法中调用父类实现的目的。


如果交换了两次岂不是又换回来了?所以我们应该用 GCD 中的 dispatch_once 来保证交换只会执行一次。

static dispatch_once_t once_Token;dispatch_once(&once_Token, ^ {    Class class = [self class];        SEL originalSelector = @selector(reloadData);    SEL swizzledSelector = @selector(swizzled_reloadData);        Method originalMethod = class_getInstanceMethod(class, originalSelector);    Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);        BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));    if (didAddMethod) {        class_addMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));    } else {        method_exchangeImplementations(originalMethod, swizzledMethod);    }});

说了这么多,这些东西写在哪里啊?写在 load 里。

也许很多人都不知道这个方法吧?我们平时开发确实不太常用到它。事实上它可是 NSObject 中的第一个方法哦:


对于一个类而言,如果没有实现 load 方法,就不会调用它,如果实现了的话,该类就会自动调用它。load 的调用时机很早。

关于 load 和 initialize,这里有详细说明:传送门


所以这段代码应该是这样的:

+ (void)load {    static dispatch_once_t once_Token;    dispatch_once(&once_Token, ^ {        Class class = [self class];                SEL originalSelector = @selector(reloadData);        SEL swizzledSelector = @selector(swizzled_reloadData);                Method originalMethod = class_getInstanceMethod(class, originalSelector);        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);                BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));        if (didAddMethod) {            class_addMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));        } else {            method_exchangeImplementations(originalMethod, swizzledMethod);        }    });}

总结一下,swizzling 可以用于我们想改变系统方法的时候。既然是黑魔法,当然也有一定的风险,保证它只执行一次、加上恰当的前缀等做法可以降低它的风险。swizzling 应该在 +load 方法中实现,load 方法会在一个类最开始加载时调用。


关于本文中提到的判断 tableView 是否为空,并在为空时做出相应处理的完整源码,请见我的github:https://github.com/963239327/LZNEmptyDataSet 别忘了点击右上角的 star 哦





2 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 魅族手机升级显示电量不足怎么办 魅蓝手机的图库没了怎么办 魅蓝5s屏幕碎了怎么办 魅族手机中间的home键失灵怎么办 魅族手机突然关机开不了机了怎么办 魅蓝3s卡顿怎么办视频 魅蓝3s手机太卡怎么办 红米4x外放破音怎么办 魅族手机刷机后内存变小了怎么办 手机用久了内存越来越小怎么办魅族 贴了车膜左右后视镜反光怎么办 贴手机膜时酒精没了怎么办 魅蓝e外屏幕碎了怎么办 贴的手机膜边缘翘起来着怎么办 手机后面贴的膜都是胶怎么办 车漆外面一层保护膜被划开了怎么办 美团骑手gps信号弱时怎么办 华为荣耀4x充电插口坏了怎么办 华为畅享5s开不了机怎么办 华为梅特10忘记账号密码怎么办 华为荣耀8充电接口坏了怎么办 华为手机锁屏了自动开屏怎么办 支付宝收货地址写错了怎么办 支付宝领海报地址写错了怎么办 手机总是弹出日历已停止运行怎么办 华为mate8忘了锁屏密码怎么办 红米4a打王者卡怎么办 苹果5s软件更新密码忘记了怎么办 华为荣耀P9进水了没有声音怎么办 华为荣耀7原相机不能拍照怎么办 华为手机触屏密码忘记了怎么办 华为荣耀10屏锁密码忘了怎么办 华为手机开机卡在开机界面怎么办 苹果6s国行不能用移动4g怎么办 苹果手机32g内存不够用怎么办 华为荣耀6x忘记了密码怎么办 百度粉色衣服被洗变色了怎么办 粉色衣服放进洗衣机洗变色了怎么办 红米note5a应用锁忘了怎么办 索尼手机死机了怎么办不可拆卸电池 华为手机一直停留在开机画面怎么办