阅读 14

Swift Tips 030 - Passing self to required Objective-C dependencies

代码截图

代码出处: Swift Tips 030 by John Sundell

小笔记

这段代码在说什么

像 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 到了么?

如果你有什么好的想法,欢迎分享!