美文网首页首页投稿iOS日常积累
使用swift泛型构建具有高测试性的网络层

使用swift泛型构建具有高测试性的网络层

作者: 蔡胜波 | 来源:发表于2018-12-10 15:58 被阅读0次

之前使用swift构建网络层使用的方法完全是Objective-C的实现方法,没有充分发挥swift的优势,在研究了objc.io这一篇博文之后才发觉自己的浅薄。

本文demo

先说一说之前网络层的实现,首先就是有一个Network的工具类,作为对网络请求库(可以是URLSessionAlamofire)的一层封装,作为实际发起网络请求的类,一般情况下,我们会针对不同的模块进一步封装相应的工具类,例如:

class StackDataTool {
    class func getAllStock(completion: @escaping ([Stock]? -> ())) {
        //Network.request...
    }
    class func getLikedStock(completion: @escaping ([Stock]? -> ())) {
        //Network.request...
    }
}

我们在这里进行一层封装的目的:

  • 可以对请求的入参(比如userId等)进行配置
  • 可以对返回数据进行处理
  • 可以返回我们需要的数据模型

但是这种方法也有弊端,它发起网络请求,解析数据,实例化对象,但是网络可能出错,解析数据可能出错,甚至还有接口返回的业务错误,这都导致测试这个方法变得困难,接下来就来简化这个模式。

首先我们来创建一个Resource的结构体(使用class类型应该也是可以的),为了简单,我们只让它包含接口的URL解析函数的属性,解析函数用来解析从服务器返回的数据。

struct Resource<T> {
    let url: URL
    let parse: (Data) -> T?
}

我们使用泛型来表示我们需要的数据类型,因为解析数据可能会失败,所以我们使用了可选类型。

在创建了一个Resource<Stock>的对象以后,接下来我们来我们来重新整理一下StackDataTool具体请求的方法(这里使用URLSession作为例子)。

class StackDataTool {

    class func load<A>(resource: Resource<A>, completion: @escaping (A?) -> ()) {
        let request = URLRequest(url: resource.url)
        let session = URLSession.shared
        let task = session.dataTask(with: request) { (data, response, error) in

            if let response = response as? HTTPURLResponse, response.statusCode >= 200 && response.statusCode < 300, let data = data {
                completion(resource.parse(data))
            } else {
                completion(nil)
            }
        }
        task.resume()
    }
}

我们就会发现,这样写这个请求方法的通用性变得很高,只要创建对应的Resource,就可以使用这个方法进行任意的网络请求并按照我们给出的解析方法解析出我们需要的数据模型,那我们不妨把这个方法做成URLSession的扩展方法。

extension URLSession {

    func load<A>(resource: Resource<A>, completion: @escaping (A?) -> ()) {

        let request = URLRequest(url: resource.url)
        let session = URLSession.shared
        let task = session.dataTask(with: request) { (data, response, error) in

            if let response = response as? HTTPURLResponse, response.statusCode >= 200 && response.statusCode < 300, let data = data {
                completion(resource.parse(data))
            } else {
                completion(nil)
            }
        }
        task.resume()
    }
}

进一步优化,我们不妨把解析的过程放进URLSession中,创建一个Result的枚举值,来表示最后的结果,Resource的解析结果就可以直接解析成success或者fail,并把数据模型或者错误信息返回,最终我们得到的就是下面这种模式。

import PlaygroundSupport
import UIKit

PlaygroundPage.current.needsIndefiniteExecution = true


enum Result<A> {
    case success(A)
    case fail(Error)
}

struct Resource<A> {
    let url: URL
    let parse: ([String: Any]) -> Result<A>
}

extension URLSession {

    func load<A>(resource: Resource<A>, completion: @escaping (Result<A>) -> ()) {

        let request = URLRequest(url: resource.url)
        let session = URLSession.shared
        let task = session.dataTask(with: request) { (data, response, error) in

            if let response = response as? HTTPURLResponse, response.statusCode >= 200 && response.statusCode < 300, let data = data {
                do {
                    let object = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: Any] ?? [:]
                    completion(resource.parse(object))
                } catch {
                    completion(.fail(error))
                }
            } else if let e = error {
                completion(.fail(e))
            }
        }
        task.resume()
    }
}

class Stock {
    var object: [String: Any]
    init(obj: [String: Any]) {
        self.object = obj
    }
}

let url = URL(string: "")!
let resource = Resource<Stock>(url: url) { (object) -> Result<Stock> in
    return .success(Stock(obj: object))
}
URLSession.shared.load(resource: resource) { (result) in
    switch result {
    case .success(let r):
        print(r.object)
    case .fail(let e):
        dump(e)
    }
}

因为针对某些固定的请求,它们的参数是固定的,因此可以对相对的对象进行扩展,方便快速获取Resource,例如:

extension Stock {
    class var allStock: Resource<Stock> {
        return Resource<Stock>(url: url) { (object) -> Result<Stock> in
            return .success(Stock(obj: object))
        }
    }
}

我们还可以对相应的错误信息进行细致的划分,例如一些服务器返回的统一错误、解析错误、业务特定错误都进行罗列:

enum ServerError: Error {
    case unknowError
    case sessionExpired
    case noAccess
}
// 解析错误
struct ParseError: Error {}

func checkForError<T>(dic: [String: Any], success:((_ dataContent: [String: Any]) -> (Result<T>))) -> Result<T> {
    let code = dic.read(key: "code", defaultValue: -1)
    let content = dic.read(key: "data", defaultValue: [String: Any]())
    switch code {
    case 0:
        return success(content)
    case -2:
        return .fail(ServerError.sessionExpired)
    case -3:
        return .fail(ServerError.noAccess)
    default:
        return .fail(ServerError.unknowError)
    }
}

let resource = Resource<Stock>(method: "GET", url: url, para: ["width" : "640", "height": "1136"], header: header) { (object) -> Result<Stock> in

    return checkForError(dic: object) { (content) -> (Result<Stock>) in
        return .success(Stock(dic: content))
    }
}

URLSession.shared.load(resource: resource) { (result) in
    switch result {
    case .success(let r):
        print(r.object)
    case .fail(let e):
        switch e {
        case let serverError as ServerError:
            switch serverError {
            case .noAccess:
                print("用户没有权限")
            case .sessionExpired:
                print("会话失效")
            default:
                print("未知错误")
            }
        case _ as ParseError:
            print("解析错误")
        default:
            print(e.localizedDescription)
        }
    }
}

相关文章

  • Swift Talk #01 Tiny Networking

    我们使用Swift泛型和结构体来构建一个简单的高可测试性的网络层。 我们聊聊Swift Talk App的网络层。...

  • 使用swift泛型构建具有高测试性的网络层

    之前使用swift构建网络层使用的方法完全是Objective-C的实现方法,没有充分发挥swift的优势,在研究...

  • Swift 运用协议泛型封装网络层

    Swift 运用协议泛型封装网络层 Swift 运用协议泛型封装网络层

  • OneDayOneSwift[23] - Generics

    泛型是 Swift 的强大特性之一,许多 Swift 标准库是通过泛型代码构建的。事实上,泛型的使用贯穿了整本语言...

  • Swift-泛型笔记

    Swift 泛型 Swift 提供了泛型让你写出灵活且可重用的函数和类型。 Swift 标准库是通过泛型代码构建出...

  • 使用Web浏览器编译Swift代码,及Swift中的泛型

    使用Web浏览器编译Swift代码,及Swift中的泛型 使用Web浏览器编译Swift代码,及Swift中的泛型

  • 泛型

    Swift 提供了泛型让你写出灵活且可重用的函数和类型。Swift 标准库是通过泛型代码构建出来的。Swift 的...

  • Swift 泛型

    Swift 提供了泛型让你写出灵活且可重用的函数和类型。Swift 标准库是通过泛型代码构建出来的。Swift 的...

  • Swift 泛型

    Swift 提供了泛型让你写出灵活且可重用的函数和类型。Swift 标准库是通过泛型代码构建出来的。Swift 的...

  • Swift 泛型

    Swift 提供了泛型让你写出灵活且可重用的函数和类型。Swift 标准库是通过泛型代码构建出来的。Swift 的...

网友评论

    本文标题:使用swift泛型构建具有高测试性的网络层

    本文链接:https://www.haomeiwen.com/subject/jrfdhqtx.html