dependencies
As shown in the figure above, Appkit is the underlying core module containing Foundation, Service, Widgets, Navigation, etc
Upper modules depend on core modules
The benefits of this:
- Easy to expand new business.
- High reuse.
- Human resources can be deployed easily.
CoreFramework – Service
Service Network layer.
Main functions:
- Unify the network request interface.
- Unified parsing of interface data.
- Handle network errors.
- Deal with token etc.
Specific implementation scheme:
Moya is recommended
The core code
ServiceProvider implements Moya Open Func Request (_ target: target, callbackQueue: DispatchQueue? = .none, progress: ProgressBlock? =. None, completion: @escaping completion) -> Cancellable
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 } }Copy the code
AppTargetType is a simplified request TargetType that inherits Moya TargetType to unify the interface
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"]
}
}
Copy the code
Json parsing
SwiftyJson is a great json parsing library
The parent class of Model in the Mapplable project automatically resolves by inheriting Mapplable
import Foundation
import SwiftyJSON
public protocol Mappable {
init(_ json:JSON)
}
Copy the code
Unified parsing class
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 } } }Copy the code
Specific usage method in Module
API is all the API interfaces of the module
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>
Copy the code
The DataProvider provides the network request portion of all interfaces in the 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)
})
}
}
Copy the code
Note: the protocol first life request method is used here. When writing mock data or unit tests, you can handle network requests directly by inheriting the DataProvider without changing any code, which will be covered in the next section.
Afterword.
This is about as much as the network layer and business modules depend on it.
The advantage of writing in this way is that the logic is clear, and the personnel who write the business module will not touch the core business code and pipelined working mode. They only need to know how to use and assemble, without considering the detailed logic processing. Can carry on the business expansion quickly. The team size can scale with the business.