调用一个动态获得的 Class 的初始化 protocol 实例方法

问题

标题有点绕,其实就是把:

  1. 通过 runtime 根据字符串获得某个 Class,用这个 Class 创建且初始化一个实例。
  2. 某个类实现了一个 protocol,这个 protocol 定义了某个自定义的 init 方法,创建出来的实例需要通过这个 init 方法来初始化。

这两种需求结合在一起。准确来说就是通过 runtime 获得了一个实现了某个 protocol 的 Class,现在创建这个 Class 的一个实例并且调用 protocol 的 init 方法来初始化。上面两个需求,单独一个都能很简单地完成,然而结合在一起之后就不是那么简单了。

通常要通过 runtime 创建一个类的实例,比较简单地一种 Objective-C 方法是:

Class cls = NSClassFromString(@"TPValueModel");
id obj = [[cls alloc] init];

然而,如果是要通过调用某个 protocol 中的 init 方法来初始化这个实例呢?于是尝试通过 Class 来声明这个 Class 实现了这个 protocol,这个没有问题,但是当用这个 cls 类对象来创建初始化实例时,Xcode 报了 error,编译时 build failed,如下图:

从上图可以看出 Xcode 给出的错误提示是无法识别 cls 中存在 alloc 类方法,这个就很奇怪了,在没有加 protocol 声明之前是可以正常编译运行的,加上 protocol 声明之后就编译失败了。这应该是在加上 protocol 声明之后,Xcode 就站在 protocol 的角度来识别这个 Class 类对象了,而在 protocol 中并没有声明 alloc 类方法,所以 Xcode 认为你调用了不存在的方法,从而编译报错

解决方法

要解决这个错误我能想到的有两种方法。

First

第一种是给 protocol 加上 alloc 类方法。既然编译器是站在 protocol 的角度识别这个 Class,那么只要我们给 protocol 加上 alloc 类方法声明就可以「欺骗」 Xcode 使之认为这个 Class 是存在这个方法的,从而可以编译通过。如下所示:

@protocol TPInitializeProtocol <NSObject>
+ (id)alloc;
- (instancetype)initWithOwnedValue;
@end

在 protocol 中加上上述代码后,终于把 Xcode 的 error 报错消除了。

Second

第二种方法是把创建和初始化分开两步,不要对 Class 声明 protocol,对创建后的实例声明 protocol,再调用 protocol 的 init 方法初始化。

Class cls = NSClassFromString(@"TPValueModel");
id<TPInitializeProtocol> unInitObj = [cls alloc];
NSObject *obj = [unInitObj initWithOwnedValue];

这样不需要给 protocol 添加代码也能解决 Xcode error 的问题。

其实这个问题是在尝试用 runtime 的反射机制来解耦代码结构时遇到的,觉得比较有趣所以记录一下。