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

2015-5-22   /   阅读数:12930   /   分类: iOS & Swift

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

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

本文中,我们将一起降低之前代码的耦合度,并使用适配器模式实现一层独立于底层结构的网络 API,造一个真正的网络请求“库”。

降低耦合度

如何降低耦合度

现在的清汤挂面式的代码虽然便于理解,但是功能单一,代码杂乱。我们一起来分析 NSURLSession 的使用过程:

  1. 构造 NSURLRequest
    1. 确定 URL
    2. 确定 HTTP 方法(GET、POST 等)
    3. 添加特定的 HTTP 头
    4. 填充 HTTP Body
  2. 驱动 session.dataTaskWithRequest 方法,开始请求

具体实施

在 Network 下另外新建一个 NetworkManager 类,将 URL、params、files 等设为成员变量,让他们在构造函数中初始化:

class NetworkManager {
    
    let method: String!
    let params: Dictionary<String, AnyObject>
    let callback: (data: NSData!, response: NSURLResponse!, error: NSError!) -> Void
    
    let session = NSURLSession.sharedSession()
    let url: String!
    var request: NSMutableURLRequest!
    var task: NSURLSessionTask!
    
    init(url: String, method: String, params: Dictionary<String, AnyObject> = Dictionary<String, AnyObject>(), callback: (data: NSData!, response: NSURLResponse!, error: NSError!) -> Void) {
        self.url = url
        self.request = NSMutableURLRequest(URL: NSURL(string: url)!)
        self.method = method
        self.params = params
        self.callback = callback
    }
}

之后,将上面分析的

1. 确定 URL

2. 确定 HTTP 方法(GET、POST 等)

3. 添加特定的 HTTP 头

4. 填充 HTTP Body

前三步封装到一个 function 中,最后一步封装到一个 function 中,然后把驱动 session.dataTaskWithRequest 的代码封装到一个 function 中:

func buildRequest() {
    if self.method == "GET" && self.params.count > 0 {
        self.request = NSMutableURLRequest(URL: NSURL(string: url + "?" + buildParams(self.params))!)
    }
    
    request.HTTPMethod = self.method
    
    if self.params.count > 0 {
        request.addValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
    }
}
func buildBody() {
    if self.params.count > 0 && self.method != "GET" {
        request.HTTPBody = buildParams(self.params).nsdata
    }
}
func fireTask() {
    task = session.dataTaskWithRequest(request, completionHandler: { (data, response, error) -> Void in
        self.callback(data: data, response: response, error: error)
    })
    task.resume()
}

之后使用一个统一的方法来驱动上面三个 function,完成请求:

func fire() {
    buildRequest()
    buildBody()
    fireTask()
}

同时,不要忘了那三个 parse params 的从 Alamofire 偷来的函数哦,也要放到这个类里面。.nsdata 属性是我对 String 做的一个扩展,代码在:https://github.com/johnlui/Swift-On-iOS/blob/master/BuildYourHTTPRequestLibrary/BuildYourHTTPRequestLibrary/Network.swift#L46-L50

至此,降低耦合的工作基本完成,接下来我们开始封装“网络API”。

使用适配器模式封装“网络API”

理解适配器模式

适配器模式是设计模式中的一种,很容易理解:我的 APP 需要一个获取某一个 URL 返回的字符串的功能,我现在选择的是 Alamofire,但是正在发展的 Pitaya 看起来不错,我以后想替换成 Pitaya,所以我封装了一层我自己的网络接口,用来屏蔽底层细节,到时候只需要修改这个类,不需要再深入项目中改那么多接口调用了。

适配器模式听起来高大上,其实这是我们在日常编码中非常常用的设计模式。

Do it!

修改 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 manager = NetworkManager(url: url, method: method, params: params, callback: callback)
        manager.fire()
    }
}

搞定!

封装多级接口

不带 params 的接口:

static func request(method: String, url: String, callback: (data: NSData!, response: NSURLResponse!, error: NSError!) -> Void) {
    let manager = NetworkManager(url: url, method: method, callback: callback)
    manager.fire()
}

两个 get 接口(带与不带 params):

static func get(url: String, callback: (data: NSData!, response: NSURLResponse!, error: NSError!) -> Void) {
    let manager = NetworkManager(url: url, method: "GET", callback: callback)
    manager.fire()
}
static func get(url: String, params: Dictionary<String, AnyObject>, callback: (data: NSData!, response: NSURLResponse!, error: NSError!) -> Void) {
    let manager = NetworkManager(url: url, method: "GET", params: params, callback: callback)
    manager.fire()
}

两个 post 接口(带与不带 params):

static func post(url: String, callback: (data: NSData!, response: NSURLResponse!, error: NSError!) -> Void) {
    let manager = NetworkManager(url: url, method: "POST", callback: callback)
    manager.fire()
}
static func post(url: String, params: Dictionary<String, AnyObject>, callback: (data: NSData!, response: NSURLResponse!, error: NSError!) -> Void) {
    let manager = NetworkManager(url: url, method: "POST", params: params, callback: callback)
    manager.fire()
}

测试接口

修改 ViewController 中的调用代码,测试多级 API:

@IBAction func mainButtonBeTapped(sender: AnyObject) {
    let url = "http://pitayaswift.sinaapp.com/pitaya.php"
    
    Network.post(url, callback: { (data, response, error) -> Void in
        println("POST 1 请求成功")
    })
    Network.post(url, params: ["post": "POST Network"], callback: { (data, response, error) -> Void in
        let string = NSString(data: data, encoding: NSUTF8StringEncoding) as! String
        println("POST 2 请求成功 " + string)
    })
    
    Network.get(url, callback: { (data, response, error) -> Void in
        println("GET 1 请求成功")
    })
    Network.get(url, params: ["get": "POST Network"], callback: { (data, response, error) -> Void in
        let string = NSString(data: data, encoding: NSUTF8StringEncoding) as! String
        println("GET 2 请求成功 " + string)
    })
    
    Network.request("GET", url: url, params: ["get": "Request Network"]) { (data, response, error) -> Void in
        let string = NSString(data: data, encoding: NSUTF8StringEncoding) as! String
        println("Request 请求成功 " + string)
    }
}


运行项目,点击按钮,查看效果:

Image

多级 API 封装成功!

下一步:自己动手写一个 iOS 网络请求库(四)——快速文件上传

WRITTEN BY

avatar

评论:

iOSQiao
2016-04-27 15:33
我这样使用会报错:
        Network.request("GET", url: "http://192.168.1.111/network.php") { (data, response, error) -> Void in
            let string = NSString(data: data!, encoding: NSUTF8StringEncoding)
            print(string)
        }
错误提示: Ambiguous use of 'request(_:url:callback:)'

加上params就行,请问博主,这是为什么?
KangKai
2015-10-01 19:39
吕老师,我想请问下,以get方法为例,不知道这样写是不是更简洁,还是说你那样写有别的考虑?
    //两个 get 接口(带与不带 params)
    static func get(url: String, callback: (data: NSData!, response: NSURLResponse!, error: NSError!) -> Void) {
        self.get(url, params: Dictionary<String, AnyObject>(),  callback: callback)
    }
    static func get(url: String, params: Dictionary<String, AnyObject>, callback: (data: NSData!, response: NSURLResponse!, error: NSError!) -> Void) {
        let manager = NetworkManager(url: url, method: "GET", params: params, callback: callback)
        manager.fire()
    }
JohnLui
2015-10-01 22:39
@KangKai:当时这么写是因为 Swift 编译器的代码提示工具做的还不是很好,现在直接给参数设置默认值,在代码提示里就会直接显示两个 api 了。
那爱离殇
2015-06-02 13:34
本人对封装网络库有一个疑问,就是要不要在封装的API里面加上网络请求的风火轮以及错误信息抛出?如果不加的话,那要在每个用到的地方都要加,如果加上的话,可能个别地方又不需要加!请指教^_^
JohnLui
2015-06-02 15:23
@那爱离殇:网络库只是一个底层库,只完成一件事。对于具体的业务,上层是可以再封装的。UI 上的菊花会动和错误信息抛出建议每个接口都单独处理。
那爱离殇
2015-06-02 15:30
@JohnLui:那简直让人崩溃呀,每个地方都写!!!
JohnLui
2015-06-02 15:54
@那爱离殇:业务需要嘛
cclv
2015-05-31 21:31
小弟遇到了一个问题:
报错:
ambiguous use of 'post'
ambiguous use of 'get'
下边这俩方法
Network.post(url, callback: { (data, response, error) -> Void in
        println("POST 1 请求成功")
    })

    Network.get(url, callback: { (data, response, error) -> Void in
        println("GET 1 请求成功")
    })
JohnLui
2015-05-31 21:43
@cclv:目测是因为:
static func post(url: String, callback: (data: NSData!, response: NSURLResponse!, error: NSError!) -> Void) {
    let manager = NetworkManager(url: url, method: "POST", callback: callback)
    manager.fire()
}
static func post(url: String, params: Dictionary<String, AnyObject>, callback: (data: NSData!, response: NSURLResponse!, error: NSError!) -> Void) {
    let manager = NetworkManager(url: url, method: "POST", params: params, callback: callback)
    manager.fire()
}
写成了一样的参数,其实是不一样的。

发表评论:

© 2011-2019 岁寒  |  Powered by Emlog