swift学习iOS Developer程序员

Swift风格的网络封装

2016-10-24  本文已影响333人  DelegateChai
本文应用到的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

上一篇下一篇

猜你喜欢

热点阅读