内容简介:许多年前,小梁进了他的第一家公司,不久迎来了他的第一个项目,他翻了下苹果的文档决定用URLSession来调后台API,于是他在每个需要和服务器交互的地方写下了如下代码:经过几期迭代,产品找到小梁同学说:“把我们项目所有的网络请求超时时间设成30s,并在所有的请求头里添加指定参数”。 小梁同学可以对每一个第一步,封装
许多年前,小梁进了他的第一家公司,不久迎来了他的第一个项目,他翻了下苹果的文档决定用URLSession来调后台API,于是他在每个需要和服务器交互的地方写下了如下代码:
class AViewController: UIViewController { func loadData() { let url: URL = "https://api.com/path?query=key" let task = URLSession.shared.dataTask(with: url) { (data, response, error) in if let error = error { self.failure(error) } else { self.success(data ?? Data()) } } task.resume() } func failure(_ error: Error) { } func success(_ data: Data) { } } 复制代码
经过几期迭代,产品找到小梁同学说:“把我们项目所有的网络请求超时时间设成30s,并在所有的请求头里添加指定参数”。
小梁同学可以对每一个 loadData()
函数进行修改,但是他现在已经编码一年,可以说得上是一个有些许经验的 程序员 了,于是他决定对项目的网络请求部分进行重构。
第一步,封装 DataLoader
类来集中管理网络请求:
class DataLoader { enum Result { case success(Data) case failure(Error) } func load(_ url: URL, completion: @escaping (Result) -> Void ) { let configuration = URLSessionConfiguration.default configuration.timeoutIntervalForRequest = 30 configuration.httpAdditionalHeaders = [ "Accept-Encoding": "acceptEncoding", "Accept-Language": "acceptLanguage", "User-Agent": "userAgent" ] let session = URLSession(configuration: configuration) let task = session.dataTask(with: url) { (data, response, error) in if let error = error { return completion(.failure(error)) } completion(.success(data ?? Data())) } task.resume() } } class AViewController: UIViewController { func loadData() { let url: URL = "https://api.com/path?query=key" DataLoader().load(url) { (result) in switch result { case .failure(let error): self.failure(error) case .success(let data): self.success(data) } } } func failure(_ error: Error) { } func success(_ data: Data) { } } 复制代码
到这里,其实已经可以满足了产品提的需求。但是小梁毕竟想表现的更“老鸟”一点,而且也想让代码更具有'swif style'于是便进行了提取协议
第二步,提取协议,这一步的目的是把请求部件移到一个协议中。代码如下
protocol NetworkEngine { typealias Handler = (Data?, URLResponse?, Error?) -> Void func request(for url: URL, completion: @escaping Handler) } extension URLSession: NetworkEngine { typealias Handler = NetworkEngine.Handler func request(for url: URL, completion: @escaping Handler) { let task = dataTask(with: url, completionHandler: completion) task.resume() } } 复制代码
如您所见, URLSession
遵守 NetworkEngine
协议,并封装了请求细节。这样,我们就可以专注于NetworkEngineAPI。
第三步,依赖项注入,现在,让我们 DataLoader
从之前更新我们使用新的 NetworkEngine
协议,并将其作为依赖项注入。我们将使用 URLSession.shared
默认参数,以便我们可以保持向后兼容性和以前一样的便利性。代码如下
class DataLoader { enum Result: Equatable { case success(Data) case failure(Error) } static var defaultEngine: URLSession = { let configuration = URLSessionConfiguration.default configuration.timeoutIntervalForRequest = 30 configuration.httpAdditionalHeaders = [ "Accept-Encoding": "acceptEncoding", "Accept-Language": "acceptLanguage", "User-Agent": "userAgent" ] let session = URLSession(configuration: configuration) return session }() private let engine: NetworkEngine init(engine: NetworkEngine = defaultEngine) { self.engine = engine } func load(_ url: URL, completion: @escaping (Result) -> Void ) { engine.request(for: url) { (data, response, error) in if let error = error { return completion(.failure(error)) } completion(.success(data ?? Data())) } } } 复制代码
重构到这里,小梁同学将利用NetworkEngine协议模拟测试,以使他的测试快速,可预测且易于维护。于是他又定义了一个Mock类
class NetworkEngineMock: NetworkEngine { typealias Handler = NetworkEngine.Handler var requestedURL: URL? func request(for url: URL, completion: @escaping Handler) { requestedURL = url let data = "Hello world".data(using: .utf8) completion(data, nil, nil) } } 复制代码
到这里,小梁觉得他功德圆满,既重构了代码,又完成了产品的需求。
####但是,他真的功德圆满吗?
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
博客秘诀:超人气博客是怎样炼成的
Darren Rowse、Chris Garrett / 向怡宁 / 人民邮电出版社 / 201005 / 39.00元
作为Web 2.0的新生事物的博客,如今已蓬勃发展,呈燎原之势,业已成为许多人的一种生活方式。中国从事博客写作的人数已达千万级,各类博客网站不可胜数。 然而,为什么有的博客人气鼎盛,拥趸众多,有的博客却门前冷落,少人问津呢?究竟应该怎样写好自己的博客,才能让它吸引更多访客的关注呢?博客网站还能为我做什么呢? 本书的两位作者长期主持知名博客站点ProBlogger.net,指导了成千上万......一起来看看 《博客秘诀:超人气博客是怎样炼成的》 这本书的介绍吧!