不知各位同学是否有感觉,类似:RxSwift
中的xxx.rx.xxx
以及Kingfisher
中的image.kf.xxx
这种api
使用起来就很爽。那么这种类似命名空间的东西是怎么实现的呢?今天一起来扒扒。我们的目标是实现字符串的截取,可以像下面这样调用:
1 |
|
啥也不管,就这样来行不行?
string.ns.substring(from: 1)
从表面看,就是调用了String
的一个属性,然后得到一个对象,这个对象上面有一个方法substring(from:)
。所以,只管梭哈:
1 |
|
nice!很简单的嘛。但是,我们要想扩展其他类,怎么办?比如扩展CGSize
。是不是要向下面这样:
1 |
|
看,还是很简单嘛!好像,只要我们想扩展某种类型,只需定义api
所依赖的对象,然后再通过extension
将其返回就可以了。真的是这样吗?让我们试试扩展Array
:
可以看到当我们需要操作具体类型的时候,就无法搞定了。当然你可能说使用类型约束,可以搞定。但是这种方式还是有太多的弊端:大量的重复代码、不方便扩展,每次新扩展类型,都需要写对应的api
包装类、不易维护。
新思路
观察上面的StringApi
、CGSizeApi
和ArrayApi
他们本质上就是对后续想操作的数据的包装。所以我们实现一个包装类:
1 |
|
这样我们就可以包装任意值。
下一步就是通过.pns
(为了和之前的ns
区别)返回这个Wrapper
对象了。这次我们选择protocol
:
1 |
|
在提供下默认实现:
1 |
|
这样,我们就可以很方便的扩展其他类型了。比如String
:
1 |
|
再比如CGSize
1 |
|
最后,我们的api
要放在何处?又该如何组织?答案还是extension
!
我们通过
.pns
返回的是Wrapper
,所以这里应该扩展Wrapper
。但是需要加上约束。
1 |
|
好了,这样就可以快乐的玩耍了。下次我们再扩展其他类型时,只需:
extension Type: NamespaceCompatible {}
extension Wrapper where T == Type
或者extension Wrapper where T: Type
RxSwift & Kingfisher
最后我们一起来看看第三方库的实现方式,是否和我们的思路一样:
RxSwift
整个实现都在Reactive.swift
文件中:
public struct Reactive<Base> {}
相当于我们这里的class Wrapper<T> {}
。选择struct
而不是class
是一个优化的点。额外的subscript
是Rx
的内容了,这里不再分析。ReactiveCompatible
相当于我们这里的NamespaceCompatible
。这里多了通过类型访问.rx
的支持。可以按需加入。
Kingfisher
该框架的实现在Kingfisher.swift
文件中:
KingfisherWrapper
跟我们的Wrapper
相同- 我们的
NamespaceCompatible
协议在这里被分成KingfisherCompatible
和KingfisherCompatibleValue
。前者专门负责引用类型,后者负责值类型。不知这样区分有何考究? - 这里的
KingfisherCompatible
和KingfisherCompatibleValue
都是空协议。这里将Wrapper
的泛型规定成了遵循协议的那个类型。 而R小Swift
中在协议中明确规定了ReactiveBase
,更加灵活。
我还是不理解。怎么办?
在如何实现这里,需要一定的抽象能力。需要多看多思考多动手。
还有一部分可能是因为 Swift 的语法问题:
好了,再也不担心我的扩展和别人的扩展冲突了!