Effective Objective-C 2.0 Tip 13: method swizzling 应用场景

得承认,每每看到别人声称 method swizzling是一个多么强大的工具,我就很纳闷,这工具的应用场景有哪些?平常我找不到它的用处,看了好几次关于这个的文章,过不久忘光光。

首先,给出一些好文章:
1.Method Swizzling
2.Method Swizzling 和 AOP 实践
站在大神的肩上学习,也许,有可能,还是搞不清楚这玩意的强大。

回顾最近看英文文章和中文文章的过程,中文文章里出现模糊性的词汇就晕菜,比如上面这篇里提到破坏代码整洁性,而相应里英文文章里讲述同样的东西,只是简单地说:

Each view controller could add tracking code to its own implementation of viewDidAppear:, but that would make for a ton of duplicated boilerplate code.

其实上面的中文文章里也讲到了这个,同样还有一些其他理由,但是,出现了那个学院派的说法后,我对这些理由的理解就消逝了。也许我由于不是科班出身,没被这种词汇熏陶过,在我接触编程过程中总是由于这类词汇的出现而懵了。还有一点,文章一定要看写得好的,不管中文英文,我这种肯定不行。很多时候,看中文看不明白,去看英文原文就明白了,得习惯看英文。
这几天看 iOS 基础的东西写博客记录,发现自己以往学习东西的特点是,这东西不知道干啥的,学起来很没效率,当初我不知道算法的用处,学习的时候也没啥动力,学过就忘,可能我骨子里就不程序员吧,至少不是那种啊这个算法好有意思我要搞懂的那种。如今有点改变,因为发现自己不会的东西都很有用处,囧。

老实说,这俩篇看下来,我几乎就没啥可写的了,不过由于我原本在看这两篇的时候都有不理解的地方,这次看恰好互补了,那么就总结一下。

应用场景

Method Swizzling 看来就像一个魔法,效果就是在运行时把调用的方法调包。那么会有哪些应用场景呢?一般来说,想要在原来的方法里加点东西或是原来的方法不合适,我们需要牵一发动你全身的效果,一劳永逸,子类化重写的缺点是你没法控制其他地方使用你的子类,而 category 重写方法无法保证重写的方法被调用,《Avoid Category Method Name Clashes》里提到:

If the name of a method declared in a category is the same as a method in the original class, or a method in another category on the same class (or even a superclass), the behavior is undefined as to which method implementation is used at runtime. This is less likely to be an issue if you’re using categories with your own classes, but can cause problems when using categories to add methods to standard Cocoa or Cocoa Touch classes.

那么 Method Swizzling 正是用来做这个的。显然这些都是针对Cocoa 里的类,有源码的类就不用费这么大劲了。
比如,我在调试 CoreData + UICollectionView 的时候,想要看到 UICollectionViewCell 的变化的具体信息,会打印 NSIndexPath 的信息,但默认的 description 方法不直观,使用 NSLog 得调整格式很繁琐,重写其 description 方法打印就好。

实现

Glow 博客写实现过程的细节更好理解,还特别贴心地解释了为何要先用 class_addMethod,看到 Matt 大神的文章这一块就很疑惑,为什么要先用 class_addMethod 而是不是直接使用 method_exchangeImplementations 呢?

Effective Objective-C 2.0 Tip 13: method swizzling 应用场景_第1张图片
使用 class_addMethod 的原因

看文档对与 class_getInstanceMethod 的解释,原来如此:

Note that this function searches superclasses for implementations, whereas class_copyMethodList does not.

对于在什么地方进行替换,Matt 的文章细节更多,终于对 +load 和 +initialize 这两方法有更多认识了,其实方法名就揭示了它们的用处。

+load vs. +initialize
Swizzling should always be done in +load.

There are two methods that are automatically invoked by the Objective-C runtime for each class. +load is sent when the class is initially loaded, while +initialize is called just before the application calls its first method on that class or an instance of that class. Both are optional, and are executed only if the method is implemented.

Because method swizzling affects global state, it is important to minimize the possibility of race conditions. +load is guaranteed to be loaded during class initialization, which provides a modicum of consistency for changing system-wide behavior. By contrast, +initialize provides no such guarantee of when it will be executed—in fact, it may never be called, if that class is never messaged directly by the app.

对于这两个方法:

+ (void)load
  • 按官方文档的说法:
    1.每个类以及它的 category 添加到 runtime 时这个方法会被调用,且只调用一次。
    2.每个类的 +load 调用有顺序依赖,在其各个超类的 +load 调用结束后。(测试表明载入时,以类似深度优先的方式载入子类,如果子类还有子类,会把继续载入子类然后最后一个子类,然后再载入同层次的其他子类。)
    3.而 category 的 +load 调用则在类的 +load 调用之后。(如果不同层次的类有 category 实现了 +load,这个调用顺序是怎样呢?测试表明,全部 category 的+load 调用都在所有类的 +load 调用完毕后才调用,而不是调用类的 +load后然后去调用该类的 category 的 +load 方法再去调用其他类;再次 category 的 +load 调用顺序和其类的调用顺序无关,没有层级和先后关系。)
  • Tip 51 里面的 addtion:
    1.执行 +load 时,runtime 出于脆弱状态(到底怎么个脆弱法),在 load 中使用其他类不安全(就是这么个脆弱法,程序启动中其他类可能还没有 load)。
    2.+load 不像普通的方法遵守继承规则,普通的方法里,如果类没有实现该方法,就询问其超类,超类没有实现,继续向上询问,直到根类 NSObject,NSObject 也没有实现的话,进入消息转发流程。那么 +load 方法,该类没有实现这个方法的话,就不会调用这个方法。
+ (void)initialize
  • 官方文档 + Tip51
    该方法在程序中第一次向该类或者该类的子类发送消息的时候调用,且也只调用一次(类本身也是对象,使用前也要初始化,只不过类都是单例对象)
    这里容易让我疑惑,文档里这么说的:

The runtime sends initialize to each class in a program just before the class, or any class that inherits from it, is sent its first message from within the program.

首次使用某个类,这个类会收到 +initialize 消息,假设之前其超类尚未被初始化过,这时候其超类也会收到 +initialize 消息吗?

The superclass implementation may be called multiple times if subclasses do not implement initialize—the runtime will call the inherited implementation—or if subclasses explicitly call [super initialize].

会出现该类初始化了,但其超类没有初始化的情况吗?(假如该类实现了 +initialize 方法,因此不用调用其超类的)
但实际情况是,往往我们使用某个子类时,生成子类的实例对象必然会调用其超类生成实例对象的方法,必然会使用该类,于是必然对超类进行初始化。对于同级别的子类,比如,某类有三个子类,使用其中一个子类时,该子类及其父类都会收到 +initialize 消息,但另外两个子类不会收到该消息。只有使用另外两个子类时,才会收到 +initialize 消息,这是惰性调用,其父类是否会收到该消息则根据上面的引用描述的那样。这么一来,文档中说的,每个类只调用一次的说法有点问题啊。实际上这句话的意思是,使用该类生成多个实例时,+initialize 方法只会调用一次。

两个方法对比:
1.+load 不支持继承,类没有实现就不会调用;而 +initialize 支持,类没有实现就调用父类的。
2.+load 在类载入的时候由 runtime 自动调用;而 +initialize 是惰性调用,使用类的时候才调用。
3.+load 只调用一次,而 +initialze 方法,虽然对每个类来说也只会自动调用一次(无论生成多少实例对象,该方法只调用一次),但情况比较复杂:1.使用子类的时候如果没有实现该方法,会调用父类的;2. 子类里显式调用父类的方法。所以 Matt 大神强烈建议在 +load 里使用 method swizzling。

另外,《Objective-C Class Loading and Initialization》值得一看。这两方法也是《iOS面试基础题目》中 class 载入以及 category 实现机制的重要基础。

你可能感兴趣的:(Effective Objective-C 2.0 Tip 13: method swizzling 应用场景)