神经病院Objective-C Runtime出院第三天——如何正确使用Runtime

458 查看

111194012-147366fc7b72e6cf

前言

到了今天终于要”出院”了,要总结一下住院几天的收获,谈谈Runtime到底能为我们开发带来些什么好处。当然它也是把双刃剑,使用不当的话,也会成为开发路上的一个大坑。

目录

  • 1.Runtime的优点
    • (1) 实现多继承Multiple Inheritance
    • (2) Method Swizzling
    • (3) Aspect Oriented Programming
    • (4) Isa Swizzling
    • (5) Associated Object关联对象
    • (6) 动态的增加方法
    • (7) NSCoding的自动归档和自动解档
    • (8) 字典和模型互相转换
  • 2.Runtime的缺点

一. 实现多继承Multiple Inheritance

在上一篇文章里面讲到的forwardingTargetForSelector:方法就能知道,一个类可以做到继承多个类的效果,只需要在这一步将消息转发给正确的类对象就可以模拟多继承的效果。

官方文档上记录了这样一段例子。

121194012-d635696fe66b05b8

在OC程序中可以借用消息转发机制来实现多继承的功能。 在上图中,一个对象对一个消息做出回应,类似于另一个对象中的方法借过来或是“继承”过来一样。 在图中,warrior实例转发了一个negotiate消息到Diplomat实例中,执行Diplomat中的negotiate方法,结果看起来像是warrior实例执行了一个和Diplomat实例一样的negotiate方法,其实执行者还是Diplomat实例。

这使得不同继承体系分支下的两个类可以“继承”对方的方法,这样一个类可以响应自己继承分支里面的方法,同时也能响应其他不相干类发过来的消息。在上图中Warrior和Diplomat没有继承关系,但是Warrior将negotiate消息转发给了Diplomat后,就好似Diplomat是Warrior的超类一样。

消息转发提供了许多类似于多继承的特性,但是他们之间有一个很大的不同:

多继承:合并了不同的行为特征在一个单独的对象中,会得到一个重量级多层面的对象。

消息转发:将各个功能分散到不同的对象中,得到的一些轻量级的对象,这些对象通过消息通过消息转发联合起来。

这里值得说明的一点是,即使我们利用转发消息来实现了“假”继承,但是NSObject类还是会将两者区分开。像respondsToSelector:和 isKindOfClass:这类方法只会考虑继承体系,不会考虑转发链。比如上图中一个Warrior对象如果被问到是否能响应negotiate消息:

结果是NO,虽然它能够响应negotiate消息而不报错,但是它是靠转发消息给Diplomat类来响应消息的。

如果非要制造假象,反应出这种“假”的继承关系,那么需要重新实现 respondsToSelector:和 isKindOfClass:来加入你的转发算法:

除了respondsToSelector:和 isKindOfClass:之外,instancesRespondToSelector:中也应该写一份转发算法。如果使用了协议,conformsToProtocol:也一样需要重写。类似地,如果一个对象转发它接受的任何远程消息,它得给出一个methodSignatureForSelector:来返回准确的方法描述,这个方法会最终响应被转发的消息。比如一个对象能给它的替代者对象转发消息,它需要像下面这样实现methodSignatureForSelector:

Note: This is an advanced technique, suitable only for situations where no other solution is possible. It is not intended as a replacement for inheritance. If you must make use of this technique, make sure you fully understand the behavior of the class doing the forwarding and the class you’re forwarding to.

需要引起注意的一点,实现methodSignatureForSelector方法是一种先进的技术,只适用于没有其他解决方案的情况下。它不会作为继承的替代。如果您必须使用这种技术,请确保您完全理解类做的转发和您转发的类的行为。请勿滥用!

二.Method Swizzling

131194012-ee492e84d125fbe3

提到Objective-C 中的 Runtime,大多数人第一个想到的可能就是黑魔法Method Swizzling。毕竟这是Runtime里面很强大的一部分,它可以通过Runtime的API实现更改任意的方法,理论上可以在运行时通过类名/方法名hook到任何 OC 方法,替换任何类的实现以及新增任意类。

举的最多的例子应该就是埋点统计用户信息的例子。

假设我们需要在页面上不同的地方统计用户信息,常见做法有两种:

  1. 傻瓜式的在所有需要统计的页面都加上代码。这样做简单,但是重复的代码太多。
  2. 把统计的代码写入基类中,比如说BaseViewController。这样虽然代码只需要写一次,但是UITableViewController,UICollectionViewcontroller都需要写一遍,这样重复的代码依旧不少。

基于这两点,我们这时候选用Method Swizzling来解决这个事情最优雅。

1. Method Swizzling原理

Method Swizzing是发生在运行时的,主要用于在运行时将两个Method进行交换,我们可以将Method Swizzling代码写到任何地方,但是只有在这段Method Swilzzling代码执行完毕之后互换才起作用。而且Method Swizzling也是iOS中AOP(面相切面编程)的一种实现方式,我们可以利用苹果这一特性来实现AOP编程。

Method Swizzling本质上就是对IMP和SEL进行交换。

2.Method Swizzling使用

一般我们使用都是新建一个分类,在分类中进行Method Swizzling方法的交换。交换的代码模板如下: