1、导入依赖
- 导入Alamofire网络框架
- 导入ObjectMapper是为了把Json解析成Object
project 'xxxxxxxx.xcodeproj'
# Uncomment the next line to define a global platform for your project
platform :ios, '9.0'
target 'xxxxxxxx' do
# Comment the next line if you're not using Swift and don't want to use dynamic frameworks
# use_frameworks!
# Pods for xxxxxxxx
# 导入Alamofire网络框架
pod 'Alamofire'
# 导入ObjectMapper是为了解析json到Object
pod 'ObjectMapper'
end
2、创建数据Model
注意:需要注意ObjectMapper的使用。
- Model类必须实现 ObjectMapper 的 Mappable 协议:
- 必须实现 required init?(map: Map) 方法(Mappable 自己的协议)
- 必须实现 func mapping(map: Map) 方法(Mappable 继承自 BaseMappable 的协议)
- 子类也必须重写 required init?(map: Map) 方法,并调用 super.init(map: map)
- 子类也必须重写 func mapping(map: Map) 方法,并调用 super.mapping(map: map)
2.1、导入ObjectMapper
import ObjectMapper
2.2、创建基类
class BaseModel: Mappable {
required init?(map: Map) {
}
func mapping(map: Map) {
}
}
2.3、创建响应错误类(继承 BaseModel)
class ApiErrorModel: BaseModel {
var code: Int?
var msg: String?
required init?(map: Map) {
super.init(map: map)
}
override func mapping(map: Map) {
super.mapping(map: map)
code <- map["code"]
msg <- map["msg"]
}
}
2.4、创建登录信息类(继承 BaseModel)
class LoginModel: BaseModel {
var token: String?
var user: User?
required init?(map: Map) {
super.init(map: map)
}
override func mapping(map: Map) {
super.mapping(map: map)
token <- map["token"]
user <- map["user"]
}
}
2.5、创建用户信息类(继承 BaseModel)
class User: BaseModel {
var id: Int?
var name: String?
var status: Int?
var createTime: Int?
required init?(map: Map) {
super.init(map: map)
}
override func mapping(map: Map) {
super.mapping(map: map)
id <- map["id"]
name <- map["name"]
status <- map["status"]
createTime <- map["createTime"]
}
}
2.6、创建包装请求结果的基类(继承 BaseModel,具体数据类型为“泛型”)
class BaseResponseWrapper: BaseModel {
var reqResult: ApiErrorModel?
func getData() -> S? {
return nil
}
required init?(map: Map) {
super.init(map: map)
}
override func mapping(map: Map) {
super.mapping(map: map)
reqResult <- map["reqResult"]
}
}
2.7、创建包装请求结果的类(继承 BaseResponseWrapper,具体数据类型为 String)
class ResponseStringWrapper: BaseResponseWrapper {
var data: String?
required init?(map: Map) {
super.init(map: map)
}
override func mapping(map: Map) {
super.mapping(map: map)
data <- map["data"]
}
override func getData() -> String? {
return data
}
}
2.8、创建包装请求结果的类(继承 BaseResponseWrapper,具体数据类型为“泛型”)
注意:泛型必须是实现 Mappable 的类,否则会解析失败
// ⚠️泛型必须是实现Mappable的类,否则会解析失败
class ResponseWrapper: BaseResponseWrapper {
var data: S?
required init?(map: Map) {
super.init(map: map)
}
override func mapping(map: Map) {
super.mapping(map: map)
data <- map["data"]
}
override func getData() -> S? {
return data
}
}
2.9、创建包装请求结果的类(继承 BaseResponseWrapper,具体数据类型为“泛型数组”)
注意:泛型必须是实现 Mappable 的类,否则会解析失败
// ⚠️泛型必须是实现Mappable的类,否则会解析失败
class ResponseListWrapper: BaseResponseWrapper<[S]> {
var data: [S]?
required init?(map: Map) {
super.init(map: map)
}
override func mapping(map: Map) {
super.mapping(map: map)
data <- map["data"]
}
override func getData() -> [S]? {
return data
}
}
3.0、创建请求错误的枚举(包括错误代码和错误消息)
enum ApiErrorType {
case INTERNAL_SERVER_ERROR
case BAD_GATEWAY
case NOT_FOUND
case CONNECTION_TIMEOUT
case NETWORK_NOT_CONNECT
case UNEXPECTED_ERROR
var rawValue: Int {
get {
switch self {
case .INTERNAL_SERVER_ERROR: return 500
case .BAD_GATEWAY: return 502
case .NOT_FOUND: return 404
case .CONNECTION_TIMEOUT: return 408
case .NETWORK_NOT_CONNECT: return 499
case .UNEXPECTED_ERROR: return 700
}
}
}
var msg: String {
get {
switch self {
case .INTERNAL_SERVER_ERROR: return "请求发生异常"
case .BAD_GATEWAY: return "请求发生异常"
case .NOT_FOUND: return "未知请求"
case .CONNECTION_TIMEOUT: return "请求超时"
case .NETWORK_NOT_CONNECT: return "网络错误"
case .UNEXPECTED_ERROR: return "未知错误"
}
}
}
public static func valueOf(rawValue: Int) -> ApiErrorType {
switch rawValue {
case ApiErrorType.INTERNAL_SERVER_ERROR.rawValue: return ApiErrorType.INTERNAL_SERVER_ERROR
case ApiErrorType.BAD_GATEWAY.rawValue: return ApiErrorType.BAD_GATEWAY
case ApiErrorType.NOT_FOUND.rawValue: return ApiErrorType.NOT_FOUND
case ApiErrorType.CONNECTION_TIMEOUT.rawValue: return ApiErrorType.CONNECTION_TIMEOUT
case ApiErrorType.NETWORK_NOT_CONNECT.rawValue: return ApiErrorType.NETWORK_NOT_CONNECT
case ApiErrorType.UNEXPECTED_ERROR.rawValue: return ApiErrorType.UNEXPECTED_ERROR
default:return ApiErrorType.UNEXPECTED_ERROR
}
}
}
3、创建网络服务
3.1、导入 Alamofire 与 ObjectMapper
import Alamofire
import ObjectMapper
3.2、配置 Alamofire 的 Https 证书
func setAlamofireHttps() {
SessionManager.default.delegate.sessionDidReceiveChallenge = { (session: URLSession, challenge: URLAuthenticationChallenge) in
let method = challenge.protectionSpace.authenticationMethod
if method == NSURLAuthenticationMethodServerTrust {
//验证服务器,直接信任或者验证证书二选一,推荐验证证书,更安全
return self.trustServerWithCer(challenge: challenge)
// return self.trustServer(challenge: challenge)
} else if method == NSURLAuthenticationMethodClientCertificate {
//认证客户端证书
return self.sendClientCer()
} else {
//其他情况,不通过验证
return (.cancelAuthenticationChallenge, nil)
}
}
}
//不做任何验证,直接信任服务器
private func trustServer(challenge: URLAuthenticationChallenge) -> (URLSession.AuthChallengeDisposition, URLCredential?) {
let disposition = URLSession.AuthChallengeDisposition.useCredential
let credential = URLCredential.init(trust: challenge.protectionSpace.serverTrust!)
return (disposition, credential)
}
//验证服务器证书
private func trustServerWithCer(challenge: URLAuthenticationChallenge) -> (URLSession.AuthChallengeDisposition, URLCredential?) {
var disposition: URLSession.AuthChallengeDisposition = .performDefaultHandling
var credential: URLCredential?
//获取服务器发送过来的证书
let serverTrust: SecTrust = challenge.protectionSpace.serverTrust!
let certificate = SecTrustGetCertificateAtIndex(serverTrust, 0)!
let remoteCertificateData = CFBridgingRetain(SecCertificateCopyData(certificate))!
//加载本地CA证书
let cerPath = Bundle.main.path(forResource: "server", ofType: "der")!
let cerUrl = URL(fileURLWithPath: cerPath)
let localCertificateData = try! Data(contentsOf: cerUrl)
if (remoteCertificateData.isEqual(localCertificateData) == true) {
//服务器证书验证通过
disposition = URLSession.AuthChallengeDisposition.useCredential
credential = URLCredential(trust: serverTrust)
} else {
//服务器证书验证失败
disposition = URLSession.AuthChallengeDisposition.cancelAuthenticationChallenge
}
return (disposition, credential)
}
//发送客户端证书交由服务器验证
private func sendClientCer() -> (URLSession.AuthChallengeDisposition, URLCredential?) {
let disposition = URLSession.AuthChallengeDisposition.useCredential
var credential: URLCredential?
//获取项目中P12证书文件的路径
let path: String = Bundle.main.path(forResource: "你本地的p12证书文件名", ofType: "p12")!
let PKCS12Data = NSData(contentsOfFile: path)!
let key: NSString = kSecImportExportPassphrase as NSString
let options: NSDictionary = [key: "p12证书的密码"] //客户端证书密码
var items: CFArray?
let error = SecPKCS12Import(PKCS12Data, options, &items)
if error == errSecSuccess {
let itemArr = items! as Array
let item = itemArr.first!
let identityPointer = item["identity"];
let secIdentityRef = identityPointer as! SecIdentity
let chainPointer = item["chain"]
let chainRef = chainPointer as? [Any]
credential = URLCredential.init(identity: secIdentityRef, certificates: chainRef, persistence: URLCredential.Persistence.forSession)
}
return (disposition, credential)
3.3、创建网络服务基类
注意:该基类接收的是“泛型”数据
- “泛型” S,即服务器返回的具体数据(“泛型” T 的 data 字段),不包括 ApiErrorModel
- ApiErrorModel 数据位于“泛型” T内(T 继承了 BaseResponseWrapper)
class BaseService> {
// 定义请求成功回调
typealias Success = (_ data: S?) -> Void
// 定义请求失败回调
typealias Failure = (_ msg: String) -> Void
// 定义 DataRequest,方便子类操控
var request: DataRequest? = nil
// 防止重复请求
var isRunning = false
// 请求参数
var parameters: [String: Any]? = nil
// 保存请求成功回调
var success: Success? = nil
// 保存请求失败回调
var failure: Failure? = nil
// 初始化
init() {
// 配置 https 证书
setAlamofireHttps()
}
// 执行网络请求(有请求参数)
// parameters:请求参数
// success:请求成功回调
// failure:请求失败回调(默认在主线程显示一个 Toast 提示)
func start(parameters: [String: Any]?, success: Success?, failure: Failure? = { (msg: String) -> Void in
DispatchQueue.main.async {
UIApplication.shared.windows.last?.makeToast(msg, duration: 1.0)
}
}) {
// 设置当前请求的参数
self.parameters = parameters
// 调用执行网络请求(无请求参数)
start(success: success, failure: failure)
}
// 执行网络请求(无请求参数)
// success:请求成功回调
// failure:请求失败回调(默认在主线程显示一个 Toast 提示)
func start(success: Success?, failure: Failure? = { (msg: String) -> Void in
DispatchQueue.main.async {
UIApplication.shared.windows.last?.makeToast(msg, duration: 1.0)
}
}) {
if isRunning {
// 正在请求中,禁止再次请求
return
}
// 设置参数:在已传进来的参数的基础上,设置一些共用参数或者网络服务子类特有的参数
setParameters()
isRunning = true
self.success = success
self.failure = failure
// 正式发送网络请求
request = Alamofire.request(
BASE_URL + requestUrl(), // 设置请求url:host + 网络服务子类自己的请求路径
method: requestMethod(), // 设置请求方法:网络服务子类自己的请求方法
parameters: parameters, // 设置请求参数:[String: Any]? 类型
encoding: URLEncoding.httpBody, // 设置URL编码,即把请求参数拼接在url后面,还是放在Body里面
headers: requestHeader()) // 设置请求Header:设置一些共用Header或者网络服务子类特有的Header
// 设置请求结果回调,在回调里解析服务器的返回信息
.responseString { response in
debugPrint("url:", BASE_URL + self.requestUrl())
debugPrint("parameters:", self.parameters?.description ?? "")
debugPrint("headers:", self.requestHeader()?.description ?? "")
debugPrint("response:", response.description)
debugPrint()
// 重置请求参数,防止下次请求被再次使用
self.parameters = nil
// 关键地方:把服务器返回的Json字符串解析为指定的泛型类(继承 BaseResponseWrapper)
let data = Mapper(context: nil, shouldIncludeNilValues: true).map(JSONString: response.value ?? "")
if response.result.isSuccess {
// 网络请求处理成功
if let data1 = data {
// 服务器有返回信息,调用请求完成的方法
self.requestFinished(data1)
self.isRunning = false
} else {
// 服务器无返回信息,调用请求失败的方法
failure?(ApiErrorType.INTERNAL_SERVER_ERROR.msg)
self.isRunning = false
}
} else {
// 网络请求处理失败,调用请求失败的方法
self.requestFailed(response)
self.isRunning = false
}
}
}
// 请求路径:这里需要网络服务子类自己重写,自己配置自己对应的路径,比如“/login”
func requestUrl() -> String {
return ""
}
// 请求方法:默认POST,但网络服务子类可以自己重写,自己配置
func requestMethod() -> HTTPMethod {
return HTTPMethod.post
}
// 请求Header:可以在这里配置所有请求共通的Header,但网络服务子类可以自己重写,自己配置自己特有的Header
func requestHeader() -> [String: String]? {
return [
"token": getUserData()?.token ?? "",
"timestamp": String(Int(NSDate().timeIntervalSince1970 * 1000))
]
}
// 请求参数:其中parameters可以通过start方法配置,然后在此基础上,再给parameters配置一些所有请求共通的参数
fileprivate func setParameters() {
if parameters == nil {
parameters = [String: Any]()
}
// 给parameters再配置一些所有请求共通的参数
parameters?.merge([
"channelId": "3"
], uniquingKeysWith: { (current, new) -> Any in
return new
})
}
// 请求完成的方法
// 参数 data:T 即对服务器返回的Json数据进行解析后的BaseResponseWrapper的实现类
private func requestFinished(_ data: T) {
// 判断BaseResponseWrapper的ApiErrorModel的错误代码,这里“1”表示服务器处理正常
if (1 == data.reqResult?.code) {
// 调用请求成功的回调
success?(data.getData())
return
}
if (-9001 == data.reqResult?.code || -9002 == data.reqResult?.code || -9002 == data.reqResult?.code) {
// 需要重新登陆
clearUserData()
}
// 调用请求失败的回调
failure?(data.reqResult?.msg ?? ApiErrorType.UNEXPECTED_ERROR.msg)
}
// 请求失败的方法
private func requestFailed(_ response: DataResponse) {
let apiErrorType: ApiErrorType = ApiErrorType.valueOf(rawValue: 0)
// 调用请求失败的回调
failure?(response.value ?? apiErrorType.msg)
}
}
3.4、创建网络服务实现类
// 通过短信验证码登陆,注意配置的枚举类型,这里是LoginModel,并使用ResponseWrapper
class LoginBySmsService: BaseService> {
static let instance = LoginBySmsService()
func start(telNo: String, verfCode: String, success: Success?) {
super.start(parameters: ["telNo": telNo, "verfCode": verfCode], success: success)
}
override func requestUrl() -> String {
// BASE_URL 后面已拼写 “/”,这里就不需要了
return "loginBySms"
}
}
// 退出登陆,注意配置的枚举类型,这里是String,并使用ResponseStringWrapper
class LogoutService: BaseService {
static let instance = LogoutService()
override func requestUrl() -> String {
// BASE_URL 后面已拼写 “/”,这里就不需要了
return "logoutPro"
}
}
// 获取用户列表,注意配置的枚举类型,这里是User数组,并使用ResponseListWrapper
class GetUserListService: BaseService<[User], ResponseListWrapper> {
static let instance = GetUserListService()
override func requestUrl() -> String {
// BASE_URL 后面已拼写 “/”,这里就不需要了
return "getUserList"
}
}
3.5、使用网络服务类发送Http请求
// 登陆
LoginBySmsService.instance.start(telNo: telNo, verfCode: verfCode,
success: { (data: LoginModel?) -> Void in
saveUserData(data: data.user)
DispatchQueue.main.async {
UIApplication.shared.windows.last?.makeToast("登录成功:用户" + (data?.user?.name ?? "") + ",欢迎您的登录", duration: 2.0)
}
})
// 获取用户列表
GetUserListService.instance.start(
success: { (data: [User]?) -> Void in
self.userList = data ?? [User]()
DispatchQueue.main.async {
self.tableView?.reloadData()
}
})
// 退出登陆
LogoutService.instance.start(
success: { (data: String?) -> Void in
clearUserData()
DispatchQueue.main.async {
UIApplication.shared.windows.last?.makeToast("退出成功", duration: 1.0)
self.pop()
}
})