自己动手写一个 iOS 网络请求库(二)——封装接口

2015-5-20   /   字数:7059   /   阅读数:33552   /   分类: iOS & Swift     

代码示例:https://github.com/johnlui/Swift-On-iOS/blob/master/BuildYourHTTPRequestLibrary 

开源项目:Pitaya,适合大文件上传的 HTTP 请求库:https://github.com/johnlui/Pitaya

本篇文章中,我们将一起尝试使用一个类来封装我们之前的代码,并尝试加入动态增加 HTTP 参数(params)的功能,之后封装出一个强大的接口。

基本封装

基础准备

新建一个 Swift 空文件,命名为 Network.swift,在里面写一个 Network 类,之后写一个静态方法 request():

class Network{
    static func request() {
        let session = NSURLSession.sharedSession()
        let request = NSURLRequest(URL: NSURL(string: "http://baidu.com")!)
        let task = session.dataTaskWithRequest(request, completionHandler: { (data, response, error) -> Void in
            println("just wait for 5 seconds!")
            sleep(5)
            let string = NSString(data: data, encoding: NSUTF8StringEncoding)
            println(string)
        })
        task.resume()
    }
}

修改 ViewController 中的按钮函数:

@IBAction func mainButtonBeTapped(sender: AnyObject) {
    Network.request()
}

运行项目,点击按钮,效果和之前一致。

自定义 HTTP method 和 URL

修改 request() 方法,将 HTTP 方法和 URL 传进去:

static func request(method: String, url: String) {
    let session = NSURLSession.sharedSession()
    let request = NSMutableURLRequest(URL: NSURL(string: url)!)
    request.HTTPMethod = method
    let task = session.dataTaskWithRequest(request, completionHandler: { (data, response, error) -> Void in
        println("just wait for 5 seconds!")
        sleep(5)
        let string = NSString(data: data, encoding: NSUTF8StringEncoding)
        println(string)
    })
    task.resume()
}

修改前面的函数调用:

@IBAction func mainButtonBeTapped(sender: AnyObject) {
    Network.request("GET", url: "http://baidu.com")
}

运行项目,点击按钮,效果和之前一致。

使用闭包处理请求结果

函数是 Swift 中的一等公民,闭包可以作为函数参数和返回值,十分强大。下面我们就用闭包来处理网络请求的返回值。修改 request() 方法,传递进去一个闭包:

static func request(method: String, url: String, callback: (data: NSData!, response: NSURLResponse!, error: NSError!) -> Void) {
    let session = NSURLSession.sharedSession()
    let request = NSMutableURLRequest(URL: NSURL(string: url)!)
    request.HTTPMethod = method
    let task = session.dataTaskWithRequest(request, completionHandler: { (data, response, error) -> Void in
        callback(data: data, response: response , error: error)
    })
    task.resume()
}

在前面函数调用处使用闭包进行结果处理:

@IBAction func mainButtonBeTapped(sender: AnyObject) {
    Network.request("GET", url: "http://baidu.com") { (data, response, error) -> Void in
        println("just wait for 5 seconds!")
        sleep(5)
        let string = NSString(data: data, encoding: NSUTF8StringEncoding)
        println(string)
    }
}

运行项目,点击按钮,效果和之前一致。

动态增加 Params

GET 方法

GET 方法下,params 在经过 url encode 之后直接附在 URL 末尾发送给服务器。修改 request() 方法,传递进去一个 params 的字典:

static func request(method: String, url: String, params: Dictionary<String, AnyObject> = Dictionary<String, AnyObject>(), callback: (data: NSData!, response: NSURLResponse!, error: NSError!) -> Void) {
    ... ...
}

为了处理 params,我们从 Alamofire 偷来他的 params 处理函数。如果是 GET 方法,那就把处理过的 params 增加到 URL 后面。Network 类的完整代码如下:

class Network{
    static func request(method: String, url: String, params: Dictionary<String, AnyObject> = Dictionary<String, AnyObject>(), callback: (data: NSData!, response: NSURLResponse!, error: NSError!) -> Void) {
        let session = NSURLSession.sharedSession()
        
        var newURL = url
        if method == "GET" {
            newURL += "?" + Network().buildParams(params)
        }
        
        let request = NSMutableURLRequest(URL: NSURL(string: newURL)!)
        request.HTTPMethod = method
        
        let task = session.dataTaskWithRequest(request, completionHandler: { (data, response, error) -> Void in
            callback(data: data, response: response , error: error)
        })
        task.resume()
    }
    
    // 从 Alamofire 偷了三个函数
    func buildParams(parameters: [String: AnyObject]) -> String {
        var components: [(String, String)] = []
        for key in sorted(Array(parameters.keys), <) {
            let value: AnyObject! = parameters[key]
            components += self.queryComponents(key, value)
        }
        
        return join("&", components.map{"\($0)=\($1)"} as [String])
    }
    func queryComponents(key: String, _ value: AnyObject) -> [(String, String)] {
        var components: [(String, String)] = []
        if let dictionary = value as? [String: AnyObject] {
            for (nestedKey, value) in dictionary {
                components += queryComponents("\(key)[\(nestedKey)]", value)
            }
        } else if let array = value as? [AnyObject] {
            for value in array {
                components += queryComponents("\(key)", value)
            }
        } else {
            components.extend([(escape(key), escape("\(value)"))])
        }
        
        return components
    }
    func escape(string: String) -> String {
        let legalURLCharactersToBeEscaped: CFStringRef = ":&=;+!@#$()',*"
        return CFURLCreateStringByAddingPercentEscapes(nil, string, nil, legalURLCharactersToBeEscaped, CFStringBuiltInEncodings.UTF8.rawValue) as String
    }
}

修改前面的函数调用:

@IBAction func mainButtonBeTapped(sender: AnyObject) {
    Network.request("GET", url: "http://pitayaswift.sinaapp.com/pitaya.php", params: ["get": "Network"]) { (data, response, error) -> Void in
        let string = NSString(data: data, encoding: NSUTF8StringEncoding)
        println(string)
    }
}

http://pitayaswift.sinaapp.com/pitaya.php 是我部署的用于测试的服务端代码,会直接返回 ?get=ooxx 中的 ooxx。运行项目,点击按钮,查看效果:

Image

POST 方法

POST 方法下有几个协议可供选择,此处没有文件上传,我们采用较简单的 application/x-www-form-urlencoded 方式发送请求。request() 方法增加一些代码:

static func request(method: String, url: String, params: Dictionary<String, AnyObject> = Dictionary<String, AnyObject>(), callback: (data: NSData!, response: NSURLResponse!, error: NSError!) -> Void) {
    let session = NSURLSession.sharedSession()
    
    var newURL = url
    if method == "GET" {
        newURL += "?" + Network().buildParams(params)
    }
    
    let request = NSMutableURLRequest(URL: NSURL(string: newURL)!)
    request.HTTPMethod = method
    
    if method == "POST" {
        request.addValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
        request.HTTPBody = Network().buildParams(params).dataUsingEncoding(NSUTF8StringEncoding)
    }
    
    let task = session.dataTaskWithRequest(request, completionHandler: { (data, response, error) -> Void in
        callback(data: data, response: response , error: error)
    })
    task.resume()
}

修改前面的函数调用:

@IBAction func mainButtonBeTapped(sender: AnyObject) {
    Network.request("POST", url: "http://pitayaswift.sinaapp.com/pitaya.php", params: ["post": "Network"]) { (data, response, error) -> Void in
        let string = NSString(data: data, encoding: NSUTF8StringEncoding)
        println(string)
    }
}

使用 POST 方式发送请求,同样服务端会返回 key 为 post 的 value 的值。运行项目,点击按钮,结果和前面 GET 方法的结果一致。

至此,接口封装完成!

下一步:自己动手写一个 iOS 网络请求库(三)——降低耦合

WRITTEN BY

avatar

评论:

GTMYang
2016-12-12 10:17
很厉害!向你学习。写的文章和代码都简洁易懂,佩服!
灰姑娘的奋斗
2016-01-12 15:58
楼主,为啥我点击github下载不了示例代码啊?
tyrad
2016-01-08 00:05
修改 escape
    //referto Alamofire:ParameterEncoding.swift line:195
    func escape(string: String) -> String {
        let generalDelimitersToEncode = ":#[]@" // does not include "?" or "/" due to RFC 3986 - Section 3.4
        let subDelimitersToEncode = "!$&'()*+,;="
        
        let allowedCharacterSet = NSCharacterSet.URLQueryAllowedCharacterSet().mutableCopy() as! NSMutableCharacterSet
        allowedCharacterSet.removeCharactersInString(generalDelimitersToEncode + subDelimitersToEncode)
        
        var escaped = ""
        
        if #available(iOS 8.3, OSX 10.10, *) {
            escaped = string.stringByAddingPercentEncodingWithAllowedCharacters(allowedCharacterSet) ?? string
        } else {
            let batchSize = 50
            var index = string.startIndex
            
            while index != string.endIndex {
                let startIndex = index
                let endIndex = index.advancedBy(batchSize, limit: string.endIndex)
                let range = Range(start: startIndex, end: endIndex)
                
                let substring = string.substringWithRange(range)
                
                escaped += substring.stringByAddingPercentEncodingWithAllowedCharacters(allowedCharacterSet) ?? substring
                
                index = endIndex
            }
        }
        
        return escaped
    }
Smartech
2015-11-24 13:51
Alamofire中的

func escape(string: String) -> String {
        let legalURLCharactersToBeEscaped: CFStringRef = ":&=;+!@#$()',*"
        return CFURLCreateStringByAddingPercentEscapes(nil, string, nil, legalURLCharactersToBeEscaped, CFStringBuiltInEncodings.UTF8.rawValue) as String
    }
已经修改为:
func escape(string: String) -> String {
        let generalDelimitersToEncode = ":#[]@" // does not include "?" or "/" due to RFC 3986 - Section 3.4
        let subDelimitersToEncode = "!$&'()*+,;="

        let allowedCharacterSet = NSCharacterSet.URLQueryAllowedCharacterSet().mutableCopy() as! NSMutableCharacterSet
        allowedCharacterSet.removeCharactersInString(generalDelimitersToEncode + subDelimitersToEncode)

        return string.stringByAddingPercentEncodingWithAllowedCharacters(allowedCharacterSet) ?? ""
    }

发表评论:

© 2011-2024 岁寒  |  Powered by Emlog