須導(dǎo)入 Alamofire HandyJSON
1. 基類模型
struct CoscoBaseModel: HandyJSON {
/// 錯(cuò)誤碼
var code: Int?
var status: Int?
/// 數(shù)據(jù)
var data: Any?
/// 提示信息
var message: String?
/// 成功 true 失敗 false
var success: Bool = false
}
2. token 模型
struct AccessToken: HandyJSON, Identifiable, Codable {
var id = UUID()
var access_token: String?
var token_type: String?
var expires_in: Double?
var storageTimestamp: TimeInterval = Date().timeIntervalSince1970
}
3. 攔截器
class CoscoRequestInterceptor: RequestInterceptor {
func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void) {
let request = sign(request: urlRequest)
completion(.success(request))
}
func retry(_ request: Request, for session: Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void) {
guard let response = request.task?.response as? HTTPURLResponse, (response.statusCode == 401) else {
/// 這個(gè)請求沒有因?yàn)?401 token 過期
/// 則進(jìn)行原始請求,不重試
return completion(.doNotRetryWithError(error))
}
// 確保只重試一次,否則會(huì)無限重試下去
guard request.retryCount == 0 else {
return completion(.doNotRetry)
}
HttpClient.getAccessToken { token in
// 緩存token
let json = try? JSONEncoder().encode(token)
UserDefaults.standard.set(json, forKey: "coscoToken")
// 1秒后進(jìn)行重試
completion(.retryWithDelay(1))
} errorCompletion: { msg in
// 刷新token失敗放棄重試
completion(.doNotRetryWithError(error))
}
}
// header 添加 token
private func sign(request: URLRequest) -> URLRequest {
guard let urlString = request.url?.absoluteString else {
return request
}
if urlString == Api.accessTokenUrl() {
return request
}
var retRequest = request
if let model = UserDefaults.standard.value(forKey: "coscoToken") {
let token = try? JSONDecoder().decode(AccessToken.self, from: model as! Data)
retRequest.setValue(token?.access_token ?? ""), forHTTPHeaderField: "Authorization")
}
return retRequest
}
}
4. 調(diào)用類和方法
class HttpClient {
class func get(_ url: String, params: [String : Any]? = nil, encoding: ParameterEncoding = URLEncoding.default) {
AF.request(url, method: .get, parameters: params, encoding: encoding, headers: nil, interceptor: CoscoRequestInterceptor(), requestModifier: nil).validate({ request, response, data in
// 401 這種返回,按照請求成功處理。所以不會(huì)觸發(fā) Retrier. 此處對特定狀態(tài)碼401返回錯(cuò)誤
let statusCode = response.statusCode
if statusCode != 401 {
return .success(())
} else {
return .failure(AFError.responseValidationFailed(reason: .unacceptableStatusCode(code: 401)))
}
}).responseString(encoding: .utf8) { response in
// 用utf8 編碼,否則中文會(huì)是亂碼
switch response.result {
case .success(let json):
guard let baseModel = CoscoBaseModel.deserialize(from: json) else {
return print("請求出錯(cuò)~")
}
print("數(shù)據(jù)--:", baseModel)
case .failure(let error):
print("錯(cuò)誤", error)
}
}
}
// 獲取token
class func getAccessToken(success: @escaping (_ token : AccessToken) -> (), errorCompletion: @escaping (_ msg : String) -> ()) {
// token 請求地址
let url = Api.accessTokenUrl()
let params = 此處為獲取token的參數(shù)
let headers: HTTPHeaders = HTTPHeaders(["Content-Type" : "application/x-www-form-urlencoded;charset=UTF-8"])
// 注意:此處是用 URL 編碼參數(shù)的 POST 請求方式,不同后端開發(fā)或許不同的參數(shù)方式,具體情況具體對待
AF.request(url, method: .post, parameters: params, encoder: URLEncodedFormParameterEncoder.default, headers: headers, interceptor: nil, requestModifier: nil).responseJSON { response in
switch response.result {
case .success(let json):
guard let tokenModel = AccessToken.deserialize(from: json as? Dictionary) else {
return errorCompletion("獲取accessToken失敗")
}
success(tokenModel)
case .failure(let error):
print(error)
errorCompletion("服務(wù)器連接出錯(cuò)啦~")
}
}
}
}
注意:
用了.responseString這個(gè)閉包,必須加上參數(shù)(encoding: .utf8),否則中文會(huì)亂碼,沒有中文的數(shù)據(jù),加不加都無所謂。
PS: 讓Swift社區(qū)豐富起來吧