Swift风格的网络封装

作者: DelegateChai | 来源:发表于2016-10-24 15:05 被阅读333次
    本文应用到的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

    相关文章

      网友评论

        本文标题:Swift风格的网络封装

        本文链接:https://www.haomeiwen.com/subject/krciyttx.html