第四周:Alamofire网络框架之二次封装

dunk

目的

  • 针对于具体项目有较完善和较方便的网络请求方案
  • 具有统一管理请求,统一管理请求错误的能力
  • 提高网络接口的阅读性和易更改性
  • 清晰的模块划分,方便之后的迁移和功能添加

应实现功能

  • 请求入口的单一化,方便管理和修改
  • 接口的灵活性,可设置请求超时时间(有默认值),请求结果分为成功和失败两个闭包,可以取消单一请求
  • 请求失败时有重新请求机制,与缓存的兼容性,与数据模型的易连接和转化性
  • 考虑到请求下载或上传体积较大的文件,断点续传问题
  • 考虑到请求在不同环境下的请求测试问题

文件分配

1,`FKRequestConfig`网络请求的配置文件,配置环境、baseURl...
2,`FKRequestURL`请求的所有URL
3,`FKRequestCentral`所有请求的入口和出口
4,`FKRequestError`处理所有请求可能出现的错误解析和相应的日志记录
5,`FKRequestManager`请求接口的实现文件,外部调用网络请求的直接接触类

具体实现

  • 配置文件FKRequestConfig
    首先先考虑到环境切换的问题,由于项目历时时间较长网络请求由之前的HTTP请求的测试环境和正式环境两个变成了现在的HTTP、HTTPS测试和正式四种环境
  • 环境配置使用结构图实现
1
2
3
4
5
6
enum EnvironmentType: String {
case formalEnvironmentHTTP = "http://xxxxxx" //正式环境http
case testEnvironmentHTTP = "http://zzzzzz" //测试环境http
case formalEnvironmentHTTPS = "https://xxxxxx" //正式环境https
case testEnvironmentHTTPS = "https://zzzzzz" //测试环境https
}

将各自对应的URL作为绑定值,这个时候就需要有获取和设置环境的方法,也可以说是获取和设置baseURL的方法。新建一个类FKEnvironment,使用单例形式,再创建一个type属性用来存放环境配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
let kEnvironmentKey: String = "com.flyco.environment"
class FKEnvironment {
//单例
static let sharedInstance = FKEnvironment()
//属性
//HTTP、HTTPS请求baseURL配置
var type: EnvironmentType {
get{
if UserDefaults.standard.value(forKey: kEnvironmentKey) != nil{
let localEnvironmentType = EnvironmentType(rawValue:UserDefaults.standard.value(forKey: kEnvironmentKey)! as! String)
return localEnvironmentType!
}else{
return .formalEnvironmentHTTP
}
}
set(newType){
UserDefaults.standard.setValue(newType.rawValue, forKey: kEnvironmentKey)
UserDefaults.standard.synchronize()
}
}
}
  • 这样就大致实现了一个配置文件所需要的基础功能
  • 接下来需要实现集中处理网络请求的类FKRequestCentral
  • 考虑到这个类为所有请求的入口和出口,必须有较高的灵活性和包容性,必要的参数大致有:url、参数、超时时间、请求方式、请求成功返回闭包、请求失败返回闭包。且需要将请求Request返回,用于需要取消请求时可以方便的取消请求。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
class API {
/**
*url:网络请求的URL
*params:请求参数
*timeout:请求超时时间,默认30s
*success:请求成功回调,已转为JSON格式
*failed:请求失败回调,返回FKError
*return:返回request,可用于取消特定网络请求,可忽略返回参数
*/
@discardableResult
static func
start(url: String,
params: [String:Any],
method: RequestMethod,
timeout: Int = 30,
success: @escaping (JSON) -> Void,
failed: @escaping (FKError) -> Void) -> DataRequest {
let requestURL = url
var requestMethod: HTTPMethod {
get{
return HTTPMethod(rawValue:method.rawValue)!
}
}
//1,responseJSON
//开始状态栏菊花动画
startStatusNetworkActivity()
let manager = customManager(timeoutInterval: TimeInterval(timeout))
let request = manager.request(requestURL,
method: requestMethod,
parameters: params).responseJSON {(returnResult) in
//停止状态栏菊花动画
stopStatusNetworkActivity()
switch returnResult.result {
case .success(let value):
success(JSON(value))
case .failure(let error):
failed(FKError.infoError(error))
}
}
return request
}
}
extension API {
//same tools method
static func startStatusNetworkActivity() {
UIApplication.shared.isNetworkActivityIndicatorVisible = true
}
static func stopStatusNetworkActivity() {
UIApplication.shared.isNetworkActivityIndicatorVisible = false
}
}

由于我们后台SSL证书还未配置完成,HTTPS请求都无法正常使用,需加入配置HTTPS请求的配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
extension API {
/**
*manager可以访问HTTPS
*/
static func customManager(timeoutInterval: TimeInterval) -> Alamofire.SessionManager {
let configuration = URLSessionConfiguration.default
configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders
configuration.timeoutIntervalForRequest = timeoutInterval
let manager = SessionManager(configuration: configuration)
manager.delegate.sessionDidReceiveChallenge = { session, challenge in
var disposition: URLSession.AuthChallengeDisposition = .performDefaultHandling
var credential: URLCredential?
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
disposition = URLSession.AuthChallengeDisposition.useCredential
credential = URLCredential(trust: challenge.protectionSpace.serverTrust! )
} else {
if challenge.previousFailureCount > 0 {
disposition = .cancelAuthenticationChallenge
} else {
credential = manager.session.configuration.urlCredentialStorage?.defaultCredential(for: challenge.protectionSpace)
if credential != nil {
disposition = .useCredential
}
}
}
return (disposition, credential)
}
return manager
}
}

由此一个较为基础的请求入口基本完成。

  • 错误解析处理FKRequestError
    这里所做的是将错误解析,提取必要信息再返回的过程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
struct FKError {
var code: Int
var url: String
var describ: String
static func infoError(_ error: Error) -> FKError {
var errorUrl: String?
var errorDiscrib: String?
let localError = error as NSError
let errorCode = localError.code
let errorUserInfo = localError.userInfo
if let u = errorUserInfo["NSErrorFailingURLStringKey"] {
errorUrl = u as? String
}else{
errorUrl = "解析出错"
}
if let d = errorUserInfo["NSLocalizedDescription"] {
errorDiscrib = d as? String
}else{
errorDiscrib = "解析出错"
}
let customError = FKError(code: errorCode, url:errorUrl!, describ: errorDiscrib!)
return customError
}
}
  • 请求的URL将集中存放在FKRequestURL中
  • 请求接口实现FKRequestManager
    将在此文件中实现网络请求的具体接口,包括URL拼接、参数拼接、请求方式和请求超时时间的设置
    举个栗子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*
*获取服务网点的省份信息
*/
@discardableResult
static func
province(success: @escaping (JSON) -> Void, failed: @escaping (FKError) -> Void)
-> DataRequest{
let params: [String:Any] = ["api_key" : "1439860175",
"api_name" : "APP",
"api_token" : "a84d0a6e3937c5b532592b1eb4f8f6b7"]
let request = API.start(url: NOBaseURL.province,
params: params,
method: .GET,
timeout: 10,
success: {success($0)},failed: {failed($0)})
return request
}

这样一个基础的网络框架基本成型,大致的功能也都具备。之后需要添加预想的功能:请求失败缓存重新请求、文件上传下载断点续传等。

总结

整个过程需要在动手写代码之前在脑中预演一遍,考虑到需要实现的功能,需要规避的问题,实现思路是怎样的,有哪些难点和重点需要注意的,再动手写代码就会得心应手很多,写出来的代码也会有较高的可读性和安全性。