谈谈如何设计一个 Network->Model 组件

大多数 APP 都需要向服务器请求数据,一般来说,一个 APP 只需要根据一个后台设计一套网络请求的封装即可。但是在开发工作中,可能一个 APP 需要接入其他产线的功能,甚至有可能同一个后台返回的接口也不能适用同一个解析规则。当出现这种情况时,MJExtensionObjectMapperHandyJSON 等模型转换的工具应运而生。

模型转换

当我们使用这些工具时,往往需要有一个确定的类型,才能完成 data 到 model 的映射。在这个阶段,一般是这样来设计模型:

class BaseRespose {
    var code: Int?
    var msg: String?
}

class UserInfo {
    var name: String?
    var age: Int?
}

class UserInfoResponse: BaseRespose {
    var data: UserInfo?
}
复制代码

这样来设计 Network:

network<T: BaseResponse>(api: String, success((data: T) -> ()))
复制代码

在这个阶段,我们运用泛型约束了模型类。使得任何继承了 BaseResponse 或实现了 BaseResponse 协议的类或结构体可以成功的解析。这样看来,似乎已经可以做到解析所有的数据结构了,但需要注意的是,此时的 Network只能处理 BaseRespose,也就意味着这时的 Network 只能处理一种类型。

举例来说,当加入新的接口,且 codemsg 的解析规则发生变化时,现在的 Network 就无法使用。

当然,在这个例子中,办法还是有的,比如:

class BaseRespose {}

class UserInfo {
    var name: String?
    var age: Int?
}

class UserInfoResponse: BaseRespose {
    var code: Int?
    var msg: String?
    var data: UserInfo?
}
复制代码

BaseRespose 不处理任何解析实现,依靠确定的类型 UserInfoResponse 进行解析,但这样你会发现,无法从 Network 内部获取 code 从而判断请求状态。进行统一的处理,其次,也会产生冗余代码。

而这种情况下,只能是增加 Network 的请求方法,来适应两种不同的结构。

同时,除了增加请求方法之外,你无法使其返回 data、string、json 等数据类型。

其次,在依靠继承关系组成模型的情况下,你也无法使用结构体来进行模型的声明。

因此,一个组件化的 Network,为了适应不同的后台或不同的数据结构,应该具备可以解析任意传入的类型,并进行输出,同时可以在 Network 的内部对请求结果进行统一的处理。且应该支持类与结构体。

下面我给大家介绍一个网络请求组件,在这个组件中,依赖了以下几个优秀的开源工具,其具体使用不再细表:

  ## 网络请求
  s.dependency 'Moya', '~> 11.0'
  ## 模型解析
  s.dependency 'ObjectMapper', '~> 3.3'		
  ## 响应式
  s.dependency 'RxSwift',    '~> 4.0'
  s.dependency 'RxCocoa',    '~> 4.0'
  ## JSON数据处理
  s.dependency 'SwiftyJSON',    '~> 4.0'
复制代码

如何针对不同后台进行设置

JingDataNetworkConfig 顾名思义。是全局的配置项。

其中 pluginsMoya 的插件机制,可以实现 log、缓存等功能。

static func handleJingDataNetworkError(_ error: JingDataNetworkError) 则是处理全局请求异常的地方。

当声明一个 Network 时,需要传入此协议的实现。

public protocol JingDataNetworkConfig {
    static var networkManager: Manager { set get }
    static var plugins: [PluginType] { set get }
    static func handleJingDataNetworkError(_ error: JingDataNetworkError)
}
复制代码

如何实现接收任意模型(解析规则)

首先需要定义 BaseResponse 协议,协议继承了 MappableMappableObjectMapper 中的一项协议,实现了 ObjectMapper 协议的类或结构体,才可以进行解析。

associatedtype DataSource 表示此协议关联了一个泛型。它的作用稍后会讲到。

func makeCustomJingDataError() -> JingDataNetworkError? 是用于制作模型解析成功后的状态错误。

public protocol JingDataNetworkBaseResponse: Mappable {
    associatedtype DataSource
    func makeCustomJingDataError() -> JingDataNetworkError?
}
复制代码

如何实现解析任意的模型

在此方法中,利用泛型约束了一个 JingDataNetworkBaseResponse 类型,并规范返回的结果也是此类型,然后将这个类型传递给 JingDataNetworkDataParser

func createObserver<R: JingDataNetworkBaseResponse>(api: T, test: Bool = false, progress: ProgressBlock? = nil) -> Observable<R> {
    return createGeneralObserver(api: api, test: test, progress: progress, success: { (ob, resp) in
        do {
            let model: R = try JingDataNetworkDataParser.handle(data: resp.data)
            ob.onNext(model)
        }
        catch let error as JingDataNetworkError {
            self.handle(ob: ob, error: error)
        }
        catch {}
    })
}
复制代码

JingDataNetworkDataParser 方法中同样传入泛型,并将这个类型传递给 JingDataNetworkResponseBuilder。在这个过程中 response.makeCustomJingDataError() 方法可以抛出一个错误交给全局和回调处理。

public static func handle<R: JingDataNetworkBaseResponse>(data: Data) throws -> R {
    guard let JSONString = String.init(data: data, encoding: .utf8) else {
        throw JingDataNetworkError.parser(.string)
    }
    guard let response: R = JingDataNetworkResponseBuilder.create(by: JSONString) else {
        throw JingDataNetworkError.parser(.model)
    }
    if let customError = response.makeCustomJingDataError() {
        throw customError
    }
    return response

复制代码

最终由下面的方法,利用 ObjectMapper 进行解析。

public static func create<R: Mappable>(by JSONString: String) -> R? {
    let mapperModel = Mapper<R>()
    let object = mapperModel.map(JSONString: JSONString)
    return object
}
复制代码

如何实现解析任意的类型

对泛型不进行约束,判断泛型的类型尝试解析。如无法解析则进入全局错误处理和错误回调。

func createObserver<R>(api: T, test: Bool = false, progress: ProgressBlock? = nil) -> Observable<R> {
        if R.Type.self == String.Type.self {
            return createGeneralObserver(api: api, test: test, progress: progress, success: { (ob, resp) in
                do {
                    let string: String = try JingDataNetworkDataParser.handle(resp: resp)
                    ob.onNext(string as! R)
                }
                catch let error as JingDataNetworkError {
                    self.handle(ob: ob, error: error)
                }
                catch {}
            })
        }
        else if R.Type.self == Data.Type.self {
            return createGeneralObserver(api: api, test: test, progress: progress, success: { (ob, resp) in
                ob.onNext(resp.data as! R)
            })
        }
        else if R.Type.self == UIImage.Type.self {
            return createGeneralObserver(api: api, test: test, progress: progress, success: { (ob, resp) in
                do {
                    let image: UIImage = try JingDataNetworkDataParser.handle(resp: resp)
                    ob.onNext(image as! R)
                }
                catch {
                    self.handle(ob: ob, error: .parser(.image))
                }
            })
        }
        else if R.Type.self == JSON.Type.self {
            return createGeneralObserver(api: api, test: test, progress: progress, success: { (ob, resp) in
                do {
                    let json: JSON = try JingDataNetworkDataParser.handle(data: resp.data)
                    ob.onNext(json as! R)
                }
                catch let error as JingDataNetworkError {
                    self.handle(ob: ob, error: error)
                }
                catch {}
            })
        }
        else {

            return createGeneralObserver(api: api, test: test, progress: progress, success: { (ob, data) in
                self.handle(ob: ob, error: .parser(.type))
            })
        }
    }
复制代码

使用示例

实现一个后台对应的配置项:

struct BaseNetworkConfig: JingDataNetworkConfig {
    static var plugins: [PluginType] = []

    static var networkManager: Manager {
        set {

        }
        get {
            let configuration = URLSessionConfiguration.default
            configuration.httpAdditionalHeaders = Manager.defaultHTTPHeaders
            configuration.timeoutIntervalForRequest = 15
            configuration.timeoutIntervalForResource = 60
            let manager = Manager(configuration: configuration)
            manager.startRequestsImmediately = false
            return manager
        }
    }

    static func handleJingDataNetworkError(_ error: JingDataNetworkError) {
        print(error.description)
    }
}
复制代码

实现一个解析规则和状态错误抛出:

class BaseResp<T: Mappable>: JingDataNetworkBaseResponse {

    typealias DataSource = T

    var data: T?
    var code: Int?

    func makeCustomJingDataError() -> JingDataNetworkError? {
        guard let c = code else { return nil }
        guard c != 0 else { return nil }
        return JingDataNetworkError.custom(code: c)
    }
}
复制代码

实现一个具体要解析的模型:

struct UserInfo: Mappable {
    var age: Int?
    var name: String?
}
复制代码

发起一个模型解析网络请求:

JingDataNetworkManager<TestApi, BaseNetworkConfig>
    .base(api: .n)
    .observer(test: true, progress: { (data) in
        print(data.progress)
    })
    .observeOn(MainScheduler.instance)
    .subscribe(onNext: { (data: BaseResp<UserInfo>) in
        print(data)
    }, onError: { (e) in
        print(e as! JingDataNetworkError)
    })
    .disposed(by: bag)
复制代码

发起一个 String 解析网络请求:

JingDataNetworkManager<TestApi, BaseNetworkConfig>
    .base(api: .n)
    .observer(test: true, progress: { (data) in
        print(data.progress)
    })
    .observeOn(MainScheduler.instance)
    .subscribe(onNext: { (data: String) in
        print(data)
    }, onError: { (e) in
        print(e as! JingDataNetworkError)
    })
    .disposed(by: bag)
复制代码

发起一个 JSON 数组的网络请求:

JingDataNetworkManager<TestApi, BaseNetworkConfig>
    .base(api: .n)
    .observer(test: true, progress: { (data) in
        print(data.progress)
    })
    .observeOn(MainScheduler.instance)
    .subscribe(onNext: { (data: JSON) in
        print(data.arrayValue.count)
    }, onError: { (e) in
        print(e as! JingDataNetworkError)
    })
    .disposed(by: bag)
复制代码

至此,我们就可以针对一个后台的不同解析规则进行适配。并可以进一步进行封装,使其使用更加简洁。

其他问题

现在我们已经可以解决上面抛出的问题。但是还有两点做的不够好,一个是在模型的解析上只能依赖 ObjectMapper 进行处理,另一方面在数组类型方面也没有特别支持。

时序管理

除去模型的解析之外,在 Network 的工作中,请求顺序的管理也是一个重头戏。其请求的顺序一般有几种情况。

  • 请求结果以相同模型解析
    • 请求回调依次响应
    • 全部请求完毕进行回调
  • 请求结果以不同模型解析
    • 请求回调依次响应
    • 全部请求完毕进行回调

下面依次来看如何进行实现。

相同模型

public func zip<R>(apis: [T], progress: ProgressBlock? = nil, test: Bool = false) -> Observable<[R]> {
    var obs = [Observable<R>]()
    for api in apis {
        let ob: Observable<R> = JingDataNetworkManager<T, C>.base(api: api).observer(test: test, progress: progress)
        obs.append(ob)
    }
    return Observable.zip(obs)
}

public func map<R>(apis: [T], progress: ProgressBlock? = nil, test: Bool = false) -> Observable<R> {
    var obs = [Observable<R>]()
    for api in apis {
        let ob: Observable<R> = JingDataNetworkManager<T, C>.base(api: api).observer(test: test, progress: progress)
        obs.append(ob)
    }
    return Observable.from(obs).merge()
}
复制代码

这里使用了 RxSwift 对请求结果分别进行打包和顺序处理。

使用示例:

JingDataNetworkSequencer<BaseNetworkConfig>.sameModel()
    .zip(apis: [TestApi.m, .n], test: true)
    .subscribe(onNext: { (d: [BaseResp<UserInfo>]) in
        print(d.map { $0.data!.name! })
    }).disposed(by: bag)
复制代码
JingDataNetworkSequencer<BaseNetworkConfig>.sameModel()
    .map(apis: [TestApi.m, .n], test: true)
    .observeOn(MainScheduler.instance)
    .subscribe(onNext: { (d: BaseResp<UserInfo>) in
        print(d.data!.name!)
    }).disposed(by: bag)
复制代码

不同模型顺序请求

不同的模型相对复杂,因为它意味着不同的后台或解析规则,同时,顺序请求时,又要求可以获取上一次请求的结果,顺序请求完成时,又可以取得最终的请求结果。

在下面的实现中:

blocks 保存每次请求的代码块,如请求失败时则会打断下一次请求。

semaphore 是信号量,保证本次 block 完成前,下一个 block 会被阻塞。

data 是本次请求的结果,用于传给下一个请求。

public class JingDataNetworkDifferentModelSequencer<C: JingDataNetworkConfig> {

    var blocks = [JingDataNetworkViodCallback]()
    let semaphore = DispatchSemaphore(value: 1)
    var data: Any?
    var bag = DisposeBag()
    var requestSuccess = true
    var results = [Any]()
    var index: Int = 0

    public func next<T: TargetType, N: JingDataNetworkBaseResponse, P>(with: @escaping (P) -> T?, progress: ProgressBlock? = nil, success: @escaping (N) -> (), error: ((Error) -> ())? = nil, test: Bool = false) -> JingDataNetworkDifferentModelSequencer {
        let api: () -> T? = {
            guard let preData = self.data as? P else { return nil }
            return with(preData)
        }
        return next(api: api, progress: progress, success: success, error: error, test: test)
    }

    public func next<T: TargetType, N: JingDataNetworkBaseResponse>(api: @escaping () -> T?, progress: ProgressBlock? = nil, success: @escaping (N) -> (), error: ((Error) -> ())? = nil, test: Bool = false) -> JingDataNetworkDifferentModelSequencer {
        let block: JingDataNetworkViodCallback = {
            guard let api = api() else {
                self.requestSuccess = false
                return
            }
            self.semaphore.wait()
            JingDataNetworkManager<T, C>.base(api: api).observer(test: test, progress: progress)
                .observeOn(MainScheduler.instance)
                .subscribe(onNext: { [weak self] (data: N) in
                    self?.data = data
                    self?.results.append(data)
                    self?.requestSuccess = true
                    success(data)
                    self?.semaphore.signal()
                    }, onError: { [weak self] (e) in
                        self?.requestSuccess = false
                        error?(e)
                        self?.semaphore.signal()
                })
                .disposed(by: self.bag)
            self.semaphore.wait()
            // print("xxxxxxxxx")
            self.semaphore.signal()
        }
        blocks.append(block)
        return self
    }

    public func run() -> PrimitiveSequence<SingleTrait, [Any]> {
        let ob = Single<[Any]>.create { (single) -> Disposable in
            let queue = DispatchQueue(label: "\(JingDataNetworkDifferentModelSequencer.self)", qos: .default, attributes: .concurrent)
            queue.async {
                for i in 0 ..< self.blocks.count {
                    self.index = i
                    guard self.requestSuccess else {
                        break
                    }
                    self.blocks[i]()
                }
                if self.requestSuccess {
                    single(.success(self.results))
                }
                else {
                    C.handleJingDataNetworkError(.sequence(.break(index: self.index)))
                    single(.error(JingDataNetworkError.sequence(.break(index: self.index))))
                }
                self.requestFinish()
            }
            return Disposables.create()
        }
        return ob
    }

    func requestFinish() {
        requestSuccess = true
        index = 0
        blocks.removeAll()
        results.removeAll()
    }
}
复制代码

示例:

JingDataNetworkSequencer<BaseNetworkConfig>.differentModel()
    .next(api: { () -> TestApi? in
        return .m
    }, success: { (data: String) in
        print(data)
    }, error: { (error) in
        print(error)
    }, test: true)
    .next(with: { (data: String) -> TestApi in
        return .m
    }, success: { (data: UserInfo) in
        print(data.data!.age!)
    }, error: { (error) in
        print(error)
    }, test: true)
    .run()
    .observeOn(MainScheduler.instance)
    .subscribe(onSuccess: { (result) in
        print("success", Thread.current)
    }) { (error) in
        print(error)
}.disposed(by: bag)
复制代码

不同模型打包请求

public extension JingDataNetworkDifferentModelSequencer {

    public func observerOfzip<T: TargetType, R: JingDataNetworkBaseResponse>(api: T, progress: ProgressBlock? = nil, test: Bool = false) -> Observable<R> {
        return JingDataNetworkManager<T, C>.base(api: api).observer(test: test, progress: progress)
    }

    public func observerOfzip<T: TargetType, R>(api: T, progress: ProgressBlock? = nil, test: Bool = false) -> Observable<R> {
        return JingDataNetworkManager<T, C>.base(api: api).observer(test: test, progress: progress)
    }
}
复制代码

不同模型的打包请求利用 RxSwift 很容易处理,示例如下:

let o1: Observable<BaseResp<UserInfo>> = JingDataNetworkSequencer<BaseNetworkConfig>.differentModel().observerOfzip(api: TestApi.m, test: true)
let o2: Observable<BaseResp<UserInfo>> = JingDataNetworkSequencer<BaseNetworkConfig>.differentModel().observerOfzip(api: TestApi.n, test: true)
let o3: Observable<String> = JingDataNetworkSequencer<BaseNetworkConfig>.differentModel().observerOfzip(api: Test2Api.n, test: true)
let o4: Observable<BaseResp<UserInfo>> = JingDataNetworkSequencer<BaseNetworkConfig>.differentModel().observerOfzip(api: Test2Api.n, test: true)
Observable.zip(o1, o2, o3, o4)
.observeOn(MainScheduler.instance)
    .subscribe(onNext: { (str1, user1, str2, user2) in
        print(Thread.current)
        print(str1, user1, str2, user2)
    }, onError: { (e) in
        print(e)
    })
.disposed(by: bag)
复制代码

项目地址

github.com/tianziyao/J…

总结

至此,关于一个网络请求的组件已经基本完成。而涉及到如下载、上传等功能,已由 Moya 进行实现。

如对你有一些帮助请点一下 star。

其中有一些设计不完善的地方,希望大家可以提 issue。

猜你喜欢

转载自juejin.im/post/5b8a7e3251882543010414db