如何在 Objective-C 中实现协议扩展

492 查看

1975281-68ab0f8f59623886

Swift 中的协议扩展为 iOS 开发带来了非常多的可能性,它为我们提供了一种类似多重继承的功能,帮助我们减少一切可能导致重复代码的地方。

关于 Protocol Extension

在 Swift 中比较出名的 Then 就是使用了协议扩展为所有的 AnyObject 添加方法,而且不需要调用 runtime 相关的 API,其实现简直是我见过最简单的开源框架之一:

只有这么几行代码,就能为所有的 NSObject 添加下面的功能:

这里没有调用任何的 runtime 相关 API,也没有在 NSObject 中进行任何的方法声明,甚至 protocol Then {} 协议本身都只有一个大括号,整个 Then 框架就是基于协议扩展来实现的。

在 Objective-C 中同样有协议,但是这些协议只是相当于接口,遵循某个协议的类只表明实现了这些接口,每个类都需要对这些接口有单独的实现,这就很可能会导致重复代码的产生。

而协议扩展可以调用协议中声明的方法,以及 where Self: AnyObject 中的 AnyObject 的类/实例方法,这就大大提高了可操作性,便于开发者写出一些意想不到的扩展。

如果读者对 Protocol Extension 兴趣或者不了解协议扩展,可以阅读最后的 Reference 了解相关内容。

ProtocolKit

其实协议扩展的强大之处就在于它能为遵循协议的类添加一些方法的实现,而不只是一些接口,而今天为各位读者介绍的 ProtocolKit 就实现了这一功能,为遵循协议的类添加方法。

ProtocolKit 的使用

我们先来看一下如何使用 ProtocolKit,首先定义一个协议:

在协议中定义了两个方法,必须实现的方法 fizz 以及可选实现 buzz,然后使用 ProtocolKit 提供的接口 defs 来定义协议中方法的实现了:

这样所有遵循 TestProtocol 协议的对象都可以调用 buzz 方法,哪怕它们没有实现:

1975281-c96d3b31961b206c

上面的 XXObject 虽然没有实现 buzz 方法,但是该方法仍然成功执行了。

ProtocolKit 的实现

ProtocolKit 的主要原理仍然是 runtime 以及宏的;通过宏的使用来隐藏类的声明以及实现的代码,然后在 main 函数运行之前,将类中的方法实现加载到内存,使用 runtime 将实现注入到目标类中。

如果你对上面的原理有所疑惑也不是太大的问题,这里只是给你一个 ProtocolKit 原理的简单描述,让你了解它是如何工作的。

ProtocolKit 中有两条重要的执行路线:

  • _pk_extension_load 将协议扩展中的方法实现加载到了内存
  • _pk_extension_inject_entry 负责将扩展协议注入到实现协议的类

加载实现

首先要解决的问题是如何将方法实现加载到内存中,这里可以先了解一下上面使用到的 defs 接口,它其实只是一个调用了其它宏的超级宏这名字是我编的

使用 defs 作为接口的是因为它是一个保留的 keyword,Xcode 会将它渲染成与 @property 等其他关键字相同的颜色。

上面的这一坨宏并不需要一个一个来分析,只需要看一下最后展开会变成什么: