Swift Tips 030 - Passing self to required Objective-C dependencies
每天了解一点不一样的 Swift 小知识
代码截图
小笔记
这段代码在说什么
像 DataLoader 和 Renderer 这样的工具类,八成是要与 Cocoa 框架打交道,例如 URLSession 的 delegate 需要继承 NSObject,CADisplayLink 的 selector 方法需要声明 @objc 关键字,这很不 Swift,为了提升使用体验,SDK 的维护者可以将其隔离在内部,就像今天的示例代码一样。
一组对比
为了让大家更好的理解今天的 tips,我们需要将示例中代码完整的呈现出来,不过由于篇幅的原因,我们只针对 DataLoader 进行详细的说明。
假设 SDK 维护者的意图是让使用者在 URL Session 的代理方法urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?)
中注入自己的逻辑,
那么这里先用方案 A,也就是 Tips 里的思路来完善代码。
// Plan A
// SDK Maintainer's Code
protocol DataLoaderADelegate: class {
func dataLoader(_ dataLoader: DataLoaderA, session: URLSession, didBecomeInvalidWithError error: Error?)
}
class DataLoaderA: NSObject {
lazy var urlSession: URLSession = self.makeURLSession()
weak var urlSessionDelegate: DataLoaderADelegate?;
private func makeURLSession() -> URLSession {
return URLSession(configuration: .default, delegate: self, delegateQueue: .main)
}
}
extension DataLoaderA: URLSessionDelegate {
internal func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?) {
urlSessionDelegate?.dataLoader(self, session: session, didBecomeInvalidWithError: error)
}
}
// SDK User's Code
class ControllerA : DataLoaderADelegate {
lazy var dataLoader: DataLoaderA = {
return DataLoaderA()
}()
func dataLoader(_ dataLoader: DataLoaderA, session: URLSession, didBecomeInvalidWithError error: Error?) {
// do something
}
}
然后我们用另外一种方式来完成这个需求:
// Plan B
// SDK Maintainer's Code
class DataLoaderB {
var urlSession: URLSession?
}
// SDK User's Code
class ControllerB: NSObject {
lazy var dataLoader: DataLoaderB = {
let dataLoader = DataLoaderB()
dataLoader.urlSession = URLSession(configuration: .default, delegate: self, delegateQueue: .main)
return dataLoader
}()
}
extension ControllerB: URLSessionDelegate {
internal func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?) {
// do something
}
}
不同的视角
我们可以看到方案 A 和方案 B 在功能上是一致的,但从 SDK 使用者的角度上,体验可是不同的。
我们可以看到方案 B 的使用者,不仅需要让自身继承关系指向 NSObject,而且也需要手动实现 URLSessionDelegate 的代理方法,虽然目前代理方法看起来需要做的不多,但如果有些不痛不痒的 required 的方法(例如需要为 DataLoader 设置一个名称),其实不如将其封装在 SDK 内部。毕竟作为 SDK 的使用者,不需要一遍一遍的去写这些无用的代码。
同样是方案 B,我们换个角度,如果你是 SDK 维护者,虽然代码写的少了,但如果想禁止开发者使用某些URLSession 的代理方法时,或者想添加一些前置逻辑时,你是不是会发现有点力不从心!
接下来,我们看一下方案 A 的使用者,他需要关心的就很少了,只需要遵守对应的代理方法即可实现对应的功能,至于 NSObject 是什么,URLSession 的代理到底有哪些,他统统都不用关心,SDK 支持的能力会在协议里一览无余。代码看起来也少了点 Objective—C 的影子。
同样是方案 A,我们再次将视角换做 SDK 的维护者,虽然代码量相对方案 B 多了许多,但是可控制性变得强了。
综上所述,虽然方案 B 在代码量上有一定优势,但从代码风格和可控性上,还是方案 A 更胜一筹。
成全别人,恶心自己
在冯小刚导演的电影《私人订制》里有这么一句经典台词:
成全别人,恶心自己
我想这句话,或许是很多基础 SDK 开发者的心声,因为在实现功能的同时,怎么让使用者在调用的时候也感觉到舒适也是需要考虑的。
不管怎样,今天的这个 tips 并不是什么奇淫巧技,更多的是在 SDK 的 API 设计上如何做到更人性化,你 get 到了么?
如果你有什么好的想法,欢迎分享!