iOS模块化架构实践(三)依赖关系&Core Service

947 阅读3分钟

依赖关系

如上图所示,Appkit 为底层核心模块 包含 Foundation ,Service , Widgets ,Navigation ,等

上层Module 依赖核心模块

这样做的好处:

  • 易于扩展新业务。
  • 高度复用。
  • 人力资源能够调配简单。

CoreFramework - Service

Service 网络层。

主要功能:

  • 统一化网络请求接口。
  • 统一解析接口数据。
  • 处理网络错误。
  • 处理token 等

具体实现方案:

推荐使用 Moya

核心code

ServiceProvider 通过继承MoyaProvider 来实现 Moya open func request(_ target: Target, callbackQueue: DispatchQueue? = .none, progress: ProgressBlock? = .none, completion: @escaping Completion) -> Cancellable的方法,这样我们可以在 completion 中来处理网络请求结果,对请求结果统一处理,

import Moya
import SwiftyJSON

/// Closure to be executed when a request has completed.
public typealias OnSuccess<T: Mappable> = (_ result: ResponseMapping<T>) -> Void
public typealias OnError = (_ error:ErrorCode) -> Void

/// Base Moay Service Provider
public class ServiceProvider<Target: TargetType> : MoyaProvider<Target> {
    
    public init(endpointClosure: @escaping EndpointClosure = MoyaProvider.defaultEndpointMapping,
                stubClosure: @escaping StubClosure = MoyaProvider.neverStub) {
        
        let networkActivityPlugin = NetworkActivityPlugin { (change, type) in
            switch change {
            case .began:
                UIApplication.shared.isNetworkActivityIndicatorVisible = true
            case .ended:
                UIApplication.shared.isNetworkActivityIndicatorVisible = false
            }
        }
        
        var plugins:[PluginType] = [networkActivityPlugin]
        #if DEBUG
        plugins.append(ConsoleLoggerPlugin())
        #endif
        
        super.init(endpointClosure: endpointClosure, stubClosure: stubClosure,plugins:plugins)
    }
    
    public func request<T:Mappable>(_ target: Target, callbackQueue: DispatchQueue? = .none, progress: ProgressBlock? = .none, onSuccess: OnSuccess<T>? = nil, onError: OnError? = nil) -> Cancellable {
        let cancellable = self.request(target, callbackQueue: callbackQueue, progress: progress) { (result) in
            switch result {
            case let .success(response):
                do {
                    let json = try response.serializeAsJson()
                    let mapping = ResponseMapping<T>(json)
                    onSuccess?(mapping)
                } catch let error {
                    if let errorCode = error as? ErrorCode {
                        onError?(errorCode)
                    } else {
                        onError?(ErrorCode.unknown)
                    }
                }
            case let .failure(error):
                var resError = ErrorCode.unknown
                switch error {
                case .underlying(let netError, let response):
                    if netError._code == -1009 {
                        resError = ErrorCode.networkError
                    }
                    if let response = response, response.statusCode == 401 {
                        resError = ErrorCode.userSessionExpired
                    }
                    fallthrough
                default:
                    resError = ErrorCode.remoteServerError
                }
                onError?(resError)
            }
        }
        return cancellable
    }
}

AppTargetType 为继承 Moya TargetType 来统一接口的request 简化 TargetType


import Foundation
import Moya

public protocol AppTargetType:TargetType {
    var parameters: [String: Any] { get }
}

public extension AppTargetType {
    var baseURL: URL {
        return URL(string: ServiceUrlConfiguration.baseUrl)!
    }
    var sampleData: Data {
        return "".data(using: .utf8)!
    }
    var validationType: ValidationType {
        return .successCodes
    }
    var task: Task {
        switch self.method {
        case .get:
            return .requestParameters(parameters: parameters, encoding: URLEncoding.default)
        default:
            return .requestParameters(parameters: parameters, encoding: JSONEncoding.default)
        }
    }
    var headers: [String : String]? {
        return [:]
        // return ["customHeaderKey":"customHeaderValue"]
    }
}

json 解析

SwiftyJson 是一个很不错的json解析的库

Mapplable 项目中model 的父类 通过继承 Mapplable 来实现自动解析

import Foundation
import SwiftyJSON

public protocol Mappable {
    init(_ json:JSON)
}

统一解析类

import SwiftyJSON

let codeKey = "code"
let messageKey = "msg"
let dataKey = "data"


/// A wrapper classs which object-mapping with response
public class ResponseMapping<T> : Mappable {
    
    public let code:Int
    public let message:String?
    
    fileprivate let json:JSON
    fileprivate var _body: T?
    
    public required init(_ json:JSON) {
        code = json[codeKey].intValue
        message = json[messageKey].stringValue
        self.json = json
    }
}

public extension ResponseMapping where T : Mappable {
    var body : T? {
        get {
            if(_body != nil) {
                return _body!
            }
            _body = T(json[dataKey])
            return _body
        }
    }
}

public extension ResponseMapping where T : Sequence, T.Element : Mappable {
    var body : T? {
        get {
            if(_body != nil) {
                return _body
            }
            if let array = json[dataKey].array {
                let result = array.map({ element -> T.Element in
                    return T.Element(element)
                })
                _body = result as? T
            }
            return _body
        }
    }
}

Module 中具体使用方法

API 为该模块的所有api 接口

import Foundation
import ModuleServices
import Moya

enum APITarget {
    case Login(username:String,pwd:String)
    case MobileLogin(phone:String,code:String)
}

extension APITarget:AppTargetType {
    var parameters: [String : Any] {
        switch self {
        case .Login(let username,let pwd):
            return ["username":username,"password":pwd]
        case .MobileLogin(let phone,let code):
            return ["phone":phone,"code":code]
        }
    }
    
    var path: String {
        switch self {
        case .Login:
            return "login/path"
        case .MobileLogin:
            return "mobile/login/path"
        }
    }
    
    var method: Moya.Method {
        return .post
    }
    
}
typealias APIRequest = ServiceProvider<APITarget>

DataProvider 提供api 中所有接口的网络请求部分。

import ModuleServices
import SwiftyJSON

protocol DataProvider {
    func login(username:String,password:String,successHandle:@escaping (LoginModel)->Void? , failHandle:@escaping (ErrorCode)->Void? )
}

struct LoginModel : Mappable {
    
    init(_ json: JSON) {
        
    }
}

class RemoteDataProvider:DataProvider {
    
    var provider = APIRequest()
    
    func login(username: String, password: String, successHandle:@escaping (LoginModel)->Void? ,failHandle:@escaping (ErrorCode)->Void?) {
        _ = provider.request(.Login(username: username, pwd: password),onSuccess: { (response:ResponseMapping<LoginModel>) in
            if let loginModel = response.body {
                successHandle(loginModel)
            }
        },onError:{ code in
            failHandle(code)
        })
    }
}

注:这里为什么要用protocol 先生命请求方法。当写mock 数据或者unit test 时 我们可以通过继承 dataprovider的方式来直接handle 网络请求,而不用改变 任何代码 这部分会放在 接下来的章节 去详细介绍。

后记

大概网络层以及 业务模块依赖网络层的部分就这么多。

这样写的好处是,逻辑清楚,编写业务模块的人员不会动到核心业务代码,流水线工作模式,只需要了解怎么使用 怎么组装,不需要考虑详细的逻辑处理。 可以快速的进行业务的扩展。 团队规模可以随业务多少进行缩放。