Swift 中的协议扩展为 iOS 开发带来了非常多的可能性,它为我们提供了一种类似多重继承的功能,帮助我们减少一切可能导致重复代码的地方。
关于 Protocol Extension
在 Swift 中比较出名的 Then 就是使用了协议扩展为所有的 AnyObject
添加方法,而且不需要调用 runtime 相关的 API,其实现简直是我见过最简单的开源框架之一:
1 2 3 4 5 6 7 8 9 10 |
public protocol Then {} extension Then where Self: AnyObject { public func then(@noescape block: Self -> Void) -> Self { block(self) return self } } extension NSObject: Then {} |
只有这么几行代码,就能为所有的 NSObject
添加下面的功能:
1 2 3 4 |
let titleLabel = UILabel().then { $0.textColor = .blackColor() $0.textAlignment = .Center } |
这里没有调用任何的 runtime 相关 API,也没有在 NSObject
中进行任何的方法声明,甚至 protocol Then {}
协议本身都只有一个大括号,整个 Then 框架就是基于协议扩展来实现的。
在 Objective-C 中同样有协议,但是这些协议只是相当于接口,遵循某个协议的类只表明实现了这些接口,每个类都需要对这些接口有单独的实现,这就很可能会导致重复代码的产生。
而协议扩展可以调用协议中声明的方法,以及 where Self: AnyObject
中的 AnyObject
的类/实例方法,这就大大提高了可操作性,便于开发者写出一些意想不到的扩展。
如果读者对 Protocol Extension 兴趣或者不了解协议扩展,可以阅读最后的 Reference 了解相关内容。
ProtocolKit
其实协议扩展的强大之处就在于它能为遵循协议的类添加一些方法的实现,而不只是一些接口,而今天为各位读者介绍的 ProtocolKit 就实现了这一功能,为遵循协议的类添加方法。
ProtocolKit 的使用
我们先来看一下如何使用 ProtocolKit,首先定义一个协议:
1 2 3 4 5 6 7 8 9 10 11 |
@protocol TestProtocol @required - (void)fizz; @optional - (void)buzz; @end |
在协议中定义了两个方法,必须实现的方法 fizz
以及可选实现 buzz
,然后使用 ProtocolKit 提供的接口 defs
来定义协议中方法的实现了:
1 2 3 4 5 6 7 |
@defs(TestProtocol) - (void)buzz { NSLog(@"Buzz"); } @end |
这样所有遵循 TestProtocol
协议的对象都可以调用 buzz
方法,哪怕它们没有实现:
上面的 XXObject
虽然没有实现 buzz
方法,但是该方法仍然成功执行了。
ProtocolKit 的实现
ProtocolKit 的主要原理仍然是 runtime 以及宏的;通过宏的使用来隐藏类的声明以及实现的代码,然后在 main 函数运行之前,将类中的方法实现加载到内存,使用 runtime 将实现注入到目标类中。
如果你对上面的原理有所疑惑也不是太大的问题,这里只是给你一个 ProtocolKit 原理的简单描述,让你了解它是如何工作的。
ProtocolKit 中有两条重要的执行路线:
_pk_extension_load
将协议扩展中的方法实现加载到了内存_pk_extension_inject_entry
负责将扩展协议注入到实现协议的类
加载实现
首先要解决的问题是如何将方法实现加载到内存中,这里可以先了解一下上面使用到的 defs
接口,它其实只是一个调用了其它宏的超级宏这名字是我编的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#define defs _pk_extension #define _pk_extension($protocol) _pk_extension_imp($protocol, _pk_get_container_class($protocol)) #define _pk_extension_imp($protocol, $container_class) \ protocol $protocol; \ @interface $container_class : NSObject @end \ @implementation $container_class \ + (void)load { \ _pk_extension_load(@protocol($protocol), $container_class.class); \ } \ #define _pk_get_container_class($protocol) _pk_get_container_class_imp($protocol, __COUNTER__) #define _pk_get_container_class_imp($protocol, $counter) _pk_get_container_class_imp_concat(__PKContainer_, $protocol, $counter) #define _pk_get_container_class_imp_concat($a, $b, $c) $a ## $b ## _ ## $c |
使用
defs
作为接口的是因为它是一个保留的 keyword,Xcode 会将它渲染成与@property
等其他关键字相同的颜色。
上面的这一坨宏并不需要一个一个来分析,只需要看一下最后展开会变成什么:
1 类似多重继承的功能,帮助我们减少一切可能导致重复代码的地方。
关于 Protocol Extension在 Swift 中比较出名的 Then 就是使用了协议扩展为所有的
只有这么几行代码,就能为所有的
这里没有调用任何的 runtime 相关 API,也没有在 在 Objective-C 中同样有协议,但是这些协议只是相当于接口,遵循某个协议的类只表明实现了这些接口,每个类都需要对这些接口有单独的实现,这就很可能会导致重复代码的产生。 而协议扩展可以调用协议中声明的方法,以及
ProtocolKit其实协议扩展的强大之处就在于它能为遵循协议的类添加一些方法的实现,而不只是一些接口,而今天为各位读者介绍的 ProtocolKit 就实现了这一功能,为遵循协议的类添加方法。 ProtocolKit 的使用我们先来看一下如何使用 ProtocolKit,首先定义一个协议:
在协议中定义了两个方法,必须实现的方法
这样所有遵循 上面的 ProtocolKit 的实现ProtocolKit 的主要原理仍然是 runtime 以及宏的;通过宏的使用来隐藏类的声明以及实现的代码,然后在 main 函数运行之前,将类中的方法实现加载到内存,使用 runtime 将实现注入到目标类中。
ProtocolKit 中有两条重要的执行路线:
加载实现首先要解决的问题是如何将方法实现加载到内存中,这里可以先了解一下上面使用到的
上面的这一坨宏并不需要一个一个来分析,只需要看一下最后展开会变成什么: |