前语
咱们开发中,除了开发UI布局相关,别的一个便是和后台沟通的数据相关业务(一般比较耗时的工作会呈现到这儿),而与他们沟通的除了咱们的嘴巴、微信,另一个便是咱们开发用的网络结构 Alamofire
了
此外开发 Alamfire
运用过程中,以前咱们可能会经常用到 JSONDecode
,在 Alamfire 5
之后,基本上就用不到了,只需求创建好与后台回来数据相似的模型,运用泛型标记主动转化即可,但实践运用过程中,关于后台回来的巨大的数据,咱们需求定制习惯咱们当前页面的新的数据结构
,因而 JSONDecode
还往达不到咱们的规范,因而引出了 HandyJSON
,当然还有其他的,这儿只介绍一个
Alamofire源码、HandyJSON源码、SwiftComponentTestDemo测验事例
下面简介一下 json 转模型结构:
ObjectMapper: 面向协议的Swift
JSON 解析结构
HandyJSON: 阿里推出的一个用于Swift
的 JSON 序列化/反序列化库。
KakaJSON: mj 新写的一个Swift
的JSON转模型东西
Alamfire
Moya 是咱们比较常用的一个基于 Alamfire
二次封装的仓库,但个人感觉没必要在额外运用一个轮子,Alamfire
现已做的许多了,直接运用 Alamfire
简易封装一个习惯到自己项目的即可,且运用更灵活,当然 Moya
看起来设置更简略(咱们对三方的依赖又多了一个),实践上运用不一定合适咱们的风格,尤其是多渠道开发的,下面简略介绍一下 Alamfire
的运用
若对 post
恳求参数类型想多一些了解,能够参考 post恳求上传数据的几种方法
Alamfire 建议 get、post恳求
运用前,先导入
import Alamofire
运用如下所示,method
不传默以为 get
,回来回调参数,能够运用泛型设定回来成果的 数据结构
//主动回来decode模型
AF.request("http://onapp.yahibo.top/public/?s=api/test/list", method: .post)
.responseDecodable {(res: AFDataResponse<ResponseList>) in
switch res.result {
case .failure(let error):
print("失利了", error)
case .success(let model):
//回来 ResponseList 类型的对象
print("model", model)
}
}
泛型 ResponseList
如下所示,解析默许遵从 JSONDecoder
哪一套,因而需求遵从 Codable
协议
//JSONDecoder转模型的设定
//假如运用简略接口,数据比较契合咱们接口,合作泛型,能够直接动态取出运用该模型
//可是碰到复杂的,嵌套,需求自行转化映射出新的数据结构,这个显然不能帮咱们一步到位,需求新的json转模型
//默许不支持泛型,像接口回来的默许的外层 status、message等结构,需求重复编写
struct ResponseList: Codable {
var status: Int = 0
var message: String?
struct item: Codable {
var id: Int
var name: String?
var headimg: String?
var description: String?
}
struct listPage: Codable {
var page: Int
var size: Int
var list: [item]?
}
var data: listPage?
}
恳求参数类型
恳求参数默许以字典的方法传递,默许 encoder
为 URLEncodedFormParameterEncoder.default
,但也能够传递运用遵从 Encodable
协议的模型,条件是 encoder
改为 JSONParameterEncoder.default
,能够依据个人编码风格来走
//默许传参运用字典
let parameters = ["username": "wwwsssddd123", "password": "kweikjkkke12dsda"]
AF.request("http://onapp.yahibo.top/public/?s=api/test/list",
method: .post, parameters: parameters)
.response { res in
}
//也支持遵从 Encodable 协议的模型,这样就不必模型转字典了(不过一般也不会独自给参数界说模型吧)
let loginInfo = LoginModel(username: "wwwsss", password: "sdfasdf")
AF.request("http://onapp.yahibo.top/public/?s=api/test/list",
method: .post, parameters: loginInfo, encoder: JSONParameterEncoder.default)
.response { res in
}
//LoginModel模型
struct LoginModel: Encodable {
var username: String
var password: String
}
传递通用 header
开发过程中,咱们可能在 header
里边添加一些基础信息,例如content-type、timestamp
之类的,或者 token
都是有可能的,因而咱们需求做一些特殊处理,统一调度
//这是一个传递 Encodable 参数的事例,而默许字典不需求这个泛型
static func post<Parameters: Encodable>(
_ convertible: URLConvertible,
body: Parameters? = nil,
headers: HTTPHeaders? = nil,
interceptor: RequestInterceptor? = nil) -> DataRequest {
//url前缀默许是相同啊了,能够这儿统一拼接,只传递后缀
let url = "\(HOST)\(convertible)"
//设置headers
var headers = HTTPHeaders();
headers.add(name: "content-type", value: "x-www-form-urlencoded")
headers.add(name: "type", value: "ios")
headers.add(name: "timestamp", value: "\(NSDate().timeIntervalSince1970)")
//回来 AF 默许的 task 供外部持续灵活调度,也能够略微处理一下
return AF.request(url, method: .post, parameters: body,
encoder: JSONParameterEncoder.default, headers: headers,
interceptor: interceptor, requestModifier: nil)
}
post恳求(query和body一起上传)
post
恳求过程中,有时候难免会碰到 query
和 body
一起传参的方法,咱们都知道 get
默许以 query
的方法传递, 而 post
默许以 body
的方法传递,有时候后台开发web和移动端公用接口,为了别离一些参数,会两种方法一起运用,因而咱们需求自行预备一下,query
参数就需求咱们自行拼接到 url
上面去了
//post上传比较特殊,默许传递body,有时也会query和body一起上传
static func post(_ convertible: URLConvertible,
body: Parameters? = nil,
parameters: Parameters? = nil,
headers: HTTPHeaders? = nil,
interceptor: RequestInterceptor? = nil) -> DataRequest {
var url = "\(HOST)\(convertible)"
if let parameters = parameters {
// query url?key1=value1&key2=value2
url = "\(url)?"
var index = 0
for (key, value) in parameters {
if index == 0 {
url = "\(url)?\(key)=\(value)"
}else {
url = "\(url)&\(key)=\(value)"
}
index += 1
}
}
var headers = HTTPHeaders();
headers.add(name: "content-type", value: "x-www-form-urlencoded")
headers.add(name: "type", value: "ios")
headers.add(name: "timestamp", value: "\(NSDate().timeIntervalSince1970)")
return AF.request(url , method: .post, parameters: body,
encoding: URLEncoding.default, headers: headers,
interceptor: interceptor, requestModifier: nil)
}
表单上传 upload
比较大的数据一般都选用表单上传(速度快),这也是咱们一致的计划,网络结构给咱们提供了一个默许的 upload 办法,咱们直接调用即可,其中 multipartFormData
需求咱们自行拼接(不了解实践怎样拼接的,能够参考文章 post恳求上传数据的几种方法)
注
:假如参数加工成了一般的 base64 字符串,能够直接默许前面 post 传递,参数当字符串即可,没必要非得 upload,这只是一种传递方法
static func upload(multipartFormData: @escaping (MultipartFormData) -> Void,
to url: URLConvertible,
parameters: [String: Data]?, //二进制的key-value信息
usingThreshold encodingMemoryThreshold: UInt64 = MultipartFormData.encodingMemoryThreshold,
method: HTTPMethod = .post,
headers: HTTPHeaders? = nil,
interceptor: RequestInterceptor? = nil,
fileManager: FileManager = .default) -> UploadRequest
{
let url = "\(HOST)\(url)"
var headers = HTTPHeaders()
headers.add(name: "content-type", value: "x-www-form-urlencoded")
headers.add(name: "type", value: "ios")
headers.add(name: "timestamp", value: "\(NSDate().timeIntervalSince1970)")
//上传事例,实践能够直接运用,需求传递 Data类型的参数
AF.upload(multipartFormData: { multipartFormData in
if let params = parameters {
for (key, value) in params {
multipartFormData.append(value, withName: key )
}
}
}, to: url, usingThreshold: encodingMemoryThreshold,
method: .post, headers: headers, interceptor: interceptor,
fileManager: fileManager, requestModifier: nil)
}
监听网络环境
假如有下载需求
时,可能需求监听用户处于 wifi
或者是 蜂窝网络
,能够依据状况让用户自行挑选,并且能够在不同状况,需求暂停和康复一些下载使命,监听代码如下所示
//这儿测验一下网络环境
let networkManager = NetworkReachabilityManager(host: "www.yahibo.top")
func testNetworkManager() {
self.networkManager!.startListening(
onQueue: DispatchQueue.global(), onUpdatePerforming: { status in
var message = ""
switch status {
case .unknown:
message = "未知网络"
case .notReachable:
message = "无法连接网络"
case .reachable(.cellular):
message = "蜂窝移动网络"
case .reachable(.ethernetOrWiFi):
message = "WIFI或有线网络"
}
print("***********\(message)*********")
})
}
网络安全验证
前面一些文章有提到 https
相关,并且现在都推荐运用 https
,因而 https
恳求相关的 ssl 证书验证
也是必要的,下面简介默许的安全验证的几种手法,咱们假如进行安全恳求,不能运用默许的 AF
了, 假如具体了解 https原理
,能够参考这篇文章 https与AFNetworking
//安全验证,设置session,校验规则
//能够将默许的AF调用换成这个能够设置成单例
fileprivate func TrustSession() -> Session{
//只要一个域名,依据自己的验证方法就填写一个就行
let policies: [String: ServerTrustEvaluating] = [
//没有本地项目证书校验,默许服务器信任评估,一起从默许主机列表证书进行校验,不校验证书是否现已失效
"www.baidu1.com": DefaultTrustEvaluator(),
//没有本地项目证书校验,查看本地证书的状况,以保证是否失效,这样做会添加开支
"www.baidu2.com": RevocationTrustEvaluator(),
//默许,校验本地项目bundle一切证书,能够设置固定证书,第一个参数
"www.baidu3.com": PinnedCertificatesTrustEvaluator(),
//默许校验本地项目一切证书的公钥,验证适宜即可
"www.baidu4.com": PublicKeysTrustEvaluator()
]
let manager = ServerTrustManager(evaluators: policies)
let session = Session(serverTrustManager: manager)
// 回来 session,就不必运用默许的 AF了,运用他即可,证书一般也不会容易变更,运用单例即可
return session
}
网络恳求接口调集(一种偏好事例)
编写网路恳求时,假如咱们想在本地有一个杰出的文档,能够像下面相同,将一个功能模块的接口封装起来,进行注释,这样代码明晰易读,调用方便,因而没必要转化成 model 方法
class Request {
//cmd + option + / 生成注释模块
/// 登陆,参数假如是以模型方法保存,也能够以模型方法上传,避免了二次生成字典传问题
/// - Parameters:
/// - username: 用户名
/// - password: 暗码
/// - successBlock: 成功回调
/// - failureBlock: 失利回调
static func login(username: String,
password: String,
successBlock: @escaping (_ model: ResponseList)->Void,
failureBlock: @escaping ()->Void) {
let paramters = [
"username": username,
"password": password
]
AFNetwork.post("https://www.baidu.com", parameters: paramters).responseDecodable {(res: AFDataResponse<ResponseList>) in
switch res.result {
case .failure(let error):
print("失利了", error)
failureBlock()
case .success(let model):
print("model", model)
if (model.status == 200) {
successBlock(model)
}
}
}
}
//cmd + option + / 生成注释模块
/// 登陆,参数假如是以模型方法保存,也能够以模型方法上传,避免了二次生成字典传问题
/// - Parameters:
/// - loginInfo: 用户信息
/// - successBlock: 成功回调
/// - failureBlock: 失利回调
static func login(loginInfo: LoginModel,
successBlock: @escaping (_ model: ListPageModel?)->Void,
failureBlock: @escaping ()->Void) {
AFNetwork.post("https://www.baidu.com", body: loginInfo).responseString { res in
switch res.result {
case .failure(let error):
print("失利了", error)
failureBlock()
case .success(let json):
//生成成果
let model = JSONDeserializer<APIResponse<ListPageModel>>.deserializeFrom(json: json)
if (model?.status == 200) {
successBlock(model?.data)
}
print("result", model ?? "")
}
}
}
}
HandyJSON
Alamofire
默许的 response
参数,信任咱们现已看到了,运用的默以为 JSONDecode
,只能按照原结构进行声明,不能映射(能够添加或者减少字段,可是不能跨层映射参数),且咱们默许的回来类型(status、message之类)重复代码,也无法用泛型规避,因而为了减少一部分重复数据结构,且能自界说属于咱们app当前功能的新的数据结构,那么 HandyJSON 将会是一个不错的挑选(当然 ObjectMapper 、 KakaJSON 也都不错,看自己偏好)
建议恳求并解析
func testHandyJson() {
let datatask = AF.request("http://onapp.yahibo.top/public/?s=api/test/list",
method: .post).responseString { res in
switch res.result {
case .failure(let error):
print("失利了", error)
case .success(let json):
//生成成果,这样直接运用,就跟 JSONDecode的相同了,但独自用来json转模型不错,究竟能够映射
//if let reponse = ResponseList.deserialize(from: jsonString) {
//print(reponse)
//}
//优化成果,运用泛型和映射,将模型生成咱们抱负中的姿态
let model = JSONDeserializer<APIResponse<ListPageModel>>
.deserializeFrom(json: json)
print("result", model ?? "")
}
}
//AF.requestTaskMap 咱们的 task在这儿,假如许多网络使命有需求撤销的话,能够走这儿
datatask.cancel()
}
上面解析的泛型模型
界说模型,需求解析的模型都需求遵从 HandyJSON
协议
//handyJSON
//运用泛型,默许回来内容如下,咱们直接剔除去外面的信息,需求
//这样咱们就不必重复编写 response 外层的结构代码了
struct APIResponse<T: HandyJSON>: HandyJSON {
var status: Int = 0
var message: String?
var data: T?
// //这儿是测验映射事例代码,当前事例不应当这么用
// var page: Int = 0
// var size: Int = 0
// //假如不是对应层级,需求进行下面映射
// //struct需求加上前面的骤变,class不需求
// mutating func mapping(mapper: HelpingMapper) {
// mapper <<<
// self.page <-- "data.page"
//
// mapper <<<
// self.size <-- "data.size"
// }
}
//咱们用到的基本模型,可能需求更多交互,运用class
class PageItem: HandyJSON {
var id: Int?
var name: String?
var headimg: String?
var description: String?
required init() {}
}
class ListPageModel: HandyJSON {
var page: Int = 0
var size: Int = 0
var list: [PageItem?]? //由于 PageItem 数据可能异常,因而解析会呈现为空状况,需求界说成可选类型
required init() {}
}
独自映射模型
除了映射网络恳求回来的数据,有时候还会独自映射数据(例如:咱们保存的默许的json数据,或者是接口某一个参数的json数据),那么咱们还以上面的 ResponseList
结构的json
为例,进行映射解析
//假定咱们只需求外层的信息,和里边的 page、size信息,那么能够将 page、size进行下面映射
struct ResponseList: HandyJSON {
var status: Int = 0
var message: String?
//这儿是是作为一个测验事例,这个事例不应当这么用
var page: Int = 0
var size: Int = 0
//假如不是对应层级,需求进行下面映射
//struct需求加上前面的骤变,class不需求
mutating func mapping(mapper: HelpingMapper) {
mapper <<<
self.page <-- "data.page"
mapper <<<
self.size <-- "data.size"
}
}
//运用代码直接解析,如下所示
if let reponse = ResponseList.deserialize(from: jsonString) {
print(reponse)
}
最后
依据偏好挑选自己喜爱的三方即可,别的杰出的扩展性、可维护行、运用遍历方面需求平衡,合适大佬们的不一定合适咱们,究竟咱们拥有的时刻成本是不相同的