Fork me on GitHub

从 xx_ 前缀向 xx. 命名空间协议转变

写在前面

最近在写新项目的时候,在一个下拉组件中看到了 .es 的语法,于是想到了很多的第三库如 Kingfisher, RxSwift 都使用了类似的 .kf, .rx 语法,从而引发了一段思考。

首先说说这个语法出现的场景,通常自己写的类或者封装的组件都会自己加前缀,但是在系统方法扩展的时候官方在 OC 时代给到的推荐是 前缀_ 的方式,在 Swift 语言出现后一段时间,开发者保留了这样的写法,但是因为 Swift 语言的发展,以及语言的特性,面向协议编程 (Protocol Oriented Programming,以下简称 POP),从而引出下面的写法。

.rx

因为是 RxSwift 的使用者,很早的时候记得当时的语法糖是 rx_, 这个是非常重度的 OC 语法推荐写法,在自己对系统方法进行扩展的时候需要加上前缀,方式方法名称重复。但是我们发现后续版本中这个语法糖做出了改变,大家可以参考 RxSwift 的这个帖子 [RxCocoa] Move from rx_ prefix to a rx. proxy (for Swift 3 update ?),这篇帖子的标题解释了一个很重要的概念,就是 rx_ 向 rx. 转变的时候是由带前缀的方法名称向协议去转变,通过拥有对象的协议去实现扩展方法。

具体实现

鉴于上面 RxSwift Issue 时间比较久远,这边提供几个近期的完整实现:

实现 Base

实现一个 Base 的 Struct/ Class,推荐 Struct。

这个 Struct 将真实的对象包裹起来,作为一个泛型结构体,不做任何实际操作。

1
2
3
4
5
6
public struct KingfisherWrapper<Base> {
public let base: Base
public init(_ base: Base) {
self.base = base
}
}

实现 Protocol 和 .kf 方法

定一个 protocol,不实现任何变量方法声明,防止其他的 protocol 继承会修改到变量方法的实现。

然后实现 protocol 的 extension,提供一个 default implementation,这边其实就是实现了一个 kf 的属性,这个属性是 KingfisherWrapper 的实例,public var kf: KingfisherWrapper<Self> Self 用在协议里面,代表的是遵守协议的对象(类/结构体/枚举)类型,即 Base 类型,根据 Base 类的不同,实现对应类里面的方法。

这样,就相当于实现了 kf 的命名空间。

1
2
3
4
5
6
7
8
9
public protocol KingfisherCompatible: AnyObject { }
extension KingfisherCompatible {
/// Gets a namespace holder for Kingfisher compatible types.
public var kf: KingfisherWrapper<Self> {
get { return KingfisherWrapper(self) }
set { }
}
}

将 Protocol 加载到所需的 Base 类并通过Extension + where Base 实现 Base 类的特定代码

将我们需要扩展的系统类遵循 protocol,这样对应的 KingfisherWrapper 对象就可以实现对应的系统类里面的方法。

在实现方法里面有个特别要注意的点,所以 UIImageView 的属性,即可以用 self. 调用的属性都需要变成 base.,因为这边的调用 .kf 的时候每次返回的都是全新的 KingfisherWrapper 实例对象,并不是调用本身对象。

因为

1
2
3
4
5
6
7
extension UIImage: KingfisherCompatible { }
extension Kingfisher where Base: UIImageView {
public func setImage(image: UIImage) {
base.image = image
}
}

使用场景

从上面的分析中来看,这样通过协议命名空间方法实现的 extension 看上去会跟优雅,也能过解决在项目中 manually 方式引入第三方库的时候,出现同名的扩展引起的冲突。但是在扩展的时候我同样发现一个问题,对于 initializers methods 是没办法使用命名空间去扩展的,我们只能对应的给一个 func 返回设置好的 Color 作为返回值。

1
2
3
4
5
6
7
8
9
10
convenience init(hex: Int, alpha: CGFloat = 1) {
var t_alpha = alpha
if t_alpha < 0 { t_alpha = 0 }
if t_alpha > 1 { t_alpha = 1 }
let red = (hex >> 16) & 0xff
let green = (hex >> 8) & 0xff
let blue = hex & 0xff
self.init(red: red, green: green, blue: blue, alpha: t_alpha)
}

版权声明



Ivan’s Blog by Ivan Ye is licensed under a Creative Commons BY-NC-ND 4.0 International License.
叶帆创作并维护的叶帆的博客博客采用创作共用保留署名-非商业-禁止演绎4.0国际许可证

本文首发于Ivan’s Blog | 叶帆的博客博客( http://yeziahehe.com ),版权所有,侵权必究。

本文链接:http://yeziahehe.com/2019/05/30/LazySequence/