Swift风格的网络封装
本文应用到的Swift特性 :枚举,函数式编程,可选项
本文是接着上一篇文章写的,网络是我们项目中一块很重要的内容,上篇文章中的写法可以满足我们简单的网络求情,但是却不够Swift。运用Swift里的enum、Monad、范型等Swift特性,可以让我们写出更Swift化的代码,这篇文章是用Swift2.3写的。
在以前的代码里,我们的网络回调Handle往往是这样写的:typealias CompletionHandle = (result:AnyObject?, error:NSError?) -> ()
func login(account:String,pwd:String,completion: (success: Bool, errorMsg: String?) -> ()) {
let loginParams= ["account": account, "password": pwd]
post(APPURLRequest("auth/login", params: loginParams)) { (success, object) -> () in
dispatch_async(dispatch_get_main_queue(), { () -> Void in
if success {
completion(success: true, errorMsg: nil)
} else {
var errorMsg= "error"
if let object = object, let passedMessage = object["message"] as? String {
errorMsg= passedMessage
}
completion(success: true, errorMsg: errorMsg)
}
})
}
}
在代码中往往需要判断是否有error或者是否有result,又或者两者都有值,这种写法在Swift里不够nice,看起来也不美观。我们的目标是这样的:
func login(params:loginParams,completion:(result:NetworkResult<NSData>) -> ()) {
NetworkManager.post("path", params: loginParams)
.response { (result) in
result
.successful({ (value) in
//登录成功 JSON解析
//拿到数据
//进一步处理
})
.failured({ (error, obj) in
//登录失败 UI处理
})
}
}
运用enum,范型
Swift里的enum可以有关联值,现在定义这样一个enum:
enum APIResult<T>{
case failure(NSError,AnyObject?)
case success(T)
func flatMap<U>( transform : (T) throws -> U?) rethrows -> APIResult<U>{
switch self {
case let .failure(error,obj):
return .failure(error,obj)
case let .success(value):
guard let newValue = try transform(value) else{
return .failure(error,obj)
}
return .success(newValue)
}
}
func map<U>( transform: (T) throws -> U) rethrows -> APIResult<U>{
return try flatMap {
try transform($0)
}
}
}
这个enum里包含了两个case,分别对应我们网络返回的result和error,另外,在enum中还写了APIResult的解包方法,方便我们解包。范型用在这里,我们可以应对网络请求以外的对错结果。接下来是网络的封装,
在Alamofire中,有两个类,一个是Request,另一个是Manager,nice的解耦思路,而且还可以用Swift漂亮的链式调用。其中Request负责对请求处理,Manager负责方法包装,线程和回调的管理。这是我们的Enum就派上用处了,completionHandle里可以用NetworkResult做为回调参数,Request可以是这样的:
class NetworkRequest:NSMutableURLRequest{
......//可以写多种处理
func response(completion:(result:NetworkResult<NSData>) -> ()) {
dataTask(self, completion: completion)
}
private func dataTask(request: NSMutableURLRequest, completion:(result:NetworkResult<NSData>) -> ()) {
let session = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration())
session.dataTaskWithRequest(request) { (data, response, error) -> Void in
//处理错误 error
if let data = data {
let response = response as? NSHTTPURLResponse
if response != nil && response!.statusCode == 200{
completion(result: NetworkResult.success(data))
} else {
//可以自定义错误 error
completion(result: NetworkResult.failed(error!, "error"))
}
}else{
completion(result: NetworkResult.failed(error!, "error"))
}
}.resume()
}
然后再封装一个Manager,对POST、GET、DownLoad、Upload等请求方式进行封装,Manager的可以是这样的:
class NetworkManager {
static let instance = NetworkManager()
class var shareInstance:NetworkManager{
return instance
}
private override init() {} // 阻止init方法
func post(path: String, params: Dictionary<String, AnyObject>? = nil) -> NetworkRequest {
//返回一个处理好的POST请求Request实例
let request = NetworkRequest(URL: NSURL(string: path)!)
request.HTTPMethod = NetworkMethodEnum.POST.rawValue
if let params = params {
var paramString = ""
for (key, value) in params {
let escapedKey = key.stringByAddingPercentEncodingWithAllowedCharacters(.URLQueryAllowedCharacterSet())
let escapedValue = value.stringByAddingPercentEncodingWithAllowedCharacters(.URLQueryAllowedCharacterSet())
paramString += "\(escapedKey)=\(escapedValue)&"
}
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.HTTPBody = paramString.dataUsingEncoding(NSUTF8StringEncoding)
}
return request
}
//还包括GET,PUT,DELETE等。。。
}
接下来,我们就可以华丽丽的调用Swift Style的网络请求了。但是之前还要做一件事情,如果直接调用的话,我们需要处理NetworkRequest的case,这样也不够Swift,我们再把NetworkRequest进行改造,将其中的解包方法改成如下:
func successful(success: (value: T) -> Void) -> NetworkRequest<T> {
switch self{
case let .success(value):
success(value: value)
default:
break
}
return self
}
func failured(failure:(error: NSError,obj: AnyObject?) -> Void) -> NetworkRequest<T> {
switch self {
case let .failed(error, obj):
failed(error, obj)
default:
break
}
return self
}
接下来就可以华丽丽的调用了,例如下:
func login(params:loginParams,completion:(result:NetworkResult<NSData>) -> ()) {
NetworkManager.post("path", params: loginParams)
.response { (result) in
result
.successful({ (value) in
//登录成功 JSON解析
//拿到数据
//进一步处理
})
.failured({ (error, obj) in
//登录失败 UI处理
})
}
}
参考自:
包涵卿在中国Swift开大者大会上的演讲
Alamofire