这里用Moya网络组件进行Network请求进行实践
Moya 依赖于Alamofire,所以还要引入Alamofire
-
API Provider
import Foundation
import Moya
import Alamofire
import ObjectMapper
// Use local mock data
let useStubbing = false
let requestTimeoutInterval = TimeInterval(60)
class APIProvider: MoyaProvider {
private static let endpointClosure = { (target: API) -> Endpoint in
return Endpoint(url: URL(target: target).absoluteString,
sampleResponseClosure: { .networkResponse(200, target.sampleData) },
method: target.method,
task: target.task,
httpHeaderFields: target.headers)
}
private static let customStubClosure = { (target: API) -> Moya.StubBehavior in
return target.stubBehavior
}
static let sharedInstance = APIProvider(useStubbing)
init(_ useStubbing: Bool) {
if useStubbing {
super.init(endpointClosure: APIProvider.endpointClosure, stubClosure: APIProvider.customStubClosure)
} else {
let configuration = URLSessionConfiguration.default
configuration.timeoutIntervalForRequest = requestTimeoutInterval
configuration.timeoutIntervalForResource = requestTimeoutInterval
super.init(endpointClosure: APIProvider.endpointClosure, stubClosure: MoyaProvider.neverStub, manager: Alamofire.SessionManager(configuration: configuration))
}
}
@discardableResult
func request(_ target: API,
successClosure: @escaping (T) -> Void,
failureClosure: @escaping (ErrorResponseCollection) -> Void) -> Cancellable {
return super.request(target) { result in
switch result {
case .success(let response):
let statusCode = response.statusCode
let data = response.data
let errorInfo = ErrorResponseCollection()
errorInfo.status = statusCode
errorInfo.api = target
switch statusCode {
case 200:
if data.count == 0 {
let result = Mapper().map(JSON: [:])
successClosure(result!)
return
}
do {
let jsonDic = try JSONSerialization.jsonObject(with: data, options: .allowFragments)
let responseDataOptional = Mapper().map(JSONObject: jsonDic)
if let responseData = responseDataOptional {
let responseDic = responseData.toJSON()
// 此处为自定义错误码,即http可正常返回200的情况下,返回自定义的提示码,比如提示用户无权限等。
let errorCode = responseDic["errorCode"] as! Int
switch errorCode {
case 10000:
errorInfo.status = 10000
failureClosure(errorInfo)
case 10101,
10102:
errorInfo.status = 400
failureClosure(errorInfo)
case 10003:
errorInfo.status = 10003
failureClosure(errorInfo)
default:
successClosure(responseData)
}
} else {
errorInfo.code = ErrorResponseCode.jsonDecodeError.rawValue
failureClosure(errorInfo)
}
} catch {
errorInfo.code = ErrorResponseCode.jsonDecodeError.rawValue
failureClosure(errorInfo)
}
case 401:
NotificationCenter.default.post(name: NSNotification.Name(rawValue: TOKENPASSDUE), object: nil)
default:
failureClosure(errorInfo)
}
case .failure(let error):
switch error {
case .underlying(let errInfo, _):
let errorInfo = ErrorResponseCollection()
errorInfo.code = errInfo._code
errorInfo.message = errInfo.localizedDescription
failureClosure(errorInfo)
default:
break
}
}
}
}
}
-
有了发网络请求的实现,现在需要包装外面一层各个API 定义
import Foundation
import Moya
private let URLs = [
"dev": "https://localhost/api",
"staging": "",
"production": ""
]
private let env = "dev"
enum API {
case updateDeviceToken(deviceToken: String)
case getPermissions()
}
extension API {
var name: String {
switch self {
case .updateDeviceToken:
return "updateDeviceToken"
case .getPermissions:
return "permissions"
}
}
}
extension API: TargetType {
var baseURL: URL {
return URL(string: URLs[env]!)!
}
var path: String {
switch self {
case .updateDeviceToken( _):
return "/device_token"
case .getPermissions:
return "/get_permissions"
}
}
var method: Moya.Method {
switch self {
case .updateDeviceToken:
return .put
default:
return .get
}
}
var task: Task {
switch self {
case
.getPermissions:
return .requestParameters(parameters: urlParameters, encoding: URLEncoding.default)
case .readNotifications,
.updateDeviceToken:
return .requestCompositeParameters(bodyParameters: bodyParameters, bodyEncoding: JSONEncoding.default, urlParameters: urlParameters)
}
}
var headers: [String : String]? {
let currentVersion = Bundle.main.infoDictionary!["CFBundleShortVersionString"] as! String
return ["appVersion": currentVersion]
}
// 设置使用mock data
var sampleData: Data {
let data = stubbedResponse(name) ?? Data()
return data
}
}
extension API {
var stubBehavior: Moya.StubBehavior {
switch self {
default:
return .delayed(seconds: 0.2)
}
}
var urlParameters: [String: Any] {
var params: [String: Any] = [:]
return params
}
var bodyParameters: [String: Any] {
var params: [String: Any] = [:]
switch self {
case .updateDeviceToken(let deviceToken):
params["token"] = deviceToken
default:
break
}
return params
}
}
// MARK: - Get mock data from json file
private func stubbedResponse(_ filename: String) -> Data? {
@objc class EmptyClass: NSObject { }
let bundle = Bundle(for: EmptyClass.self)
let pathOptional = bundle.path(forResource: filename, ofType: "json")
guard let path = pathOptional else { return nil }
do {
return try Data(contentsOf: URL(fileURLWithPath: path))
} catch {
return nil
}
}
-
这样就可以用provider对某个api发出请求
在Manager中, 用非逃逸闭包形式发出请求,API Provider 为单例模式,只需要调用其request方法即可,得到的结果可能是请求成功(进入successClosure,得到的数据会依照responseCollection定义的格式进行map)或者请求失败(进入failureClosure,对错误信息进行单独处理)
func getPermission(completion: @escaping (_ result: Bool, _ errMsg: String?) -> Void) {
let target = API.getPermissions()
APIProvider.sharedInstance.request(target, successClosure: { (response: permissionResponseCollection) in
guard let result = response.result else {
completion(false, I18nUtil.localized(GET_PERMISSION_FAILED))
return
}
completion(true, nil)
}, failureClosure: {(errResponse: ErrorResponseCollection) in
if let statusCode = errResponse.status {
switch statusCode {
default:
completion(false, errResponse.errorDisplayMessage)
return
}
}
completion(false, errResponse.errorDisplayMessage)
})
}
最终都会调用闭包中的completion方法,将请求结果返回给view controller中调用之处,显示出来。