MQTT 協(xié)議核心角色
MQTT 協(xié)議主要有三大核心角色:發(fā)布者(Publisher)、Broker代理服務(wù)器(轉(zhuǎn)發(fā)者)、訂閱者(Subscriber)棕所。其中消息的發(fā)布者和訂閱者都是客戶(hù)端角色焕参,消息代理是服務(wù)器,消息發(fā)布者可以同時(shí)是訂閱者响蕴。
1、 MQTT客戶(hù)端
MQTT客戶(hù)端身兼二職:既可以是發(fā)布者角色,又可以是訂閱者角色铣墨。一個(gè)使用MQTT協(xié)議的應(yīng)用程序或者設(shè)備就是一個(gè)MQTT 客戶(hù)端,工作時(shí)它需要主動(dòng)去連接到代理服務(wù)器办绝,所以MQTT客戶(hù)端的功能有:
主動(dòng)與Broker 建立連接伊约,主動(dòng)斷開(kāi)Broker的連接。
作為發(fā)布者角色孕蝉,發(fā)布給其他客戶(hù)端訂閱的Topic
作為訂閱者屡律,主動(dòng)訂閱其它客戶(hù)端發(fā)布的Topic
退訂之前訂閱的Topic
清空服務(wù)端之前保留的Message
2、 MQTT服務(wù)器
MQTT通信必須依賴(lài)一個(gè)MQTT Broker降淮,Broker(服務(wù)器)可以看出是MQTT網(wǎng)絡(luò)的Hub超埋,負(fù)責(zé)處理客戶(hù)端的訂閱邏輯并轉(zhuǎn)發(fā)給其他訂閱的客戶(hù)端,如下圖所示佳鳖。
MQTT服務(wù)器又稱(chēng)為"消息代理"服務(wù)器(Broker)霍殴,可以是一個(gè)應(yīng)用程序或一臺(tái)設(shè)備,它是位于消息發(fā)布者和訂閱者之間系吩,具有以下功能:
接受來(lái)自客戶(hù)端的網(wǎng)絡(luò)連接并建立通信鏈路
接收發(fā)布者的Topic并轉(zhuǎn)發(fā)給訂閱者
處理來(lái)自客戶(hù)端的訂閱和退訂請(qǐng)求
向訂閱的客戶(hù)轉(zhuǎn)發(fā)相應(yīng)地Topic
-----------------------------------------------------------------------------------------分割線(xiàn)-----------------------------------------------------------------------------------------------
面向協(xié)議
先看代碼:
protocol Run {
var name: String { get }
func run()
}
代碼中定義名為Run協(xié)議,包含一個(gè)name屬性,以及一個(gè)run方法的定義 所謂協(xié)議来庭,就是屬性的定義和方法的聲明(注意這里只需要聲明即可),而如果某個(gè)具體類(lèi)型想要遵守一個(gè)協(xié)議穿挨,那它需要實(shí)現(xiàn)這個(gè)協(xié)議所定義的所有內(nèi)容
POP就是通過(guò)協(xié)議擴(kuò)展月弛,協(xié)議繼承和協(xié)議組合的方式來(lái)設(shè)計(jì)需要編寫(xiě)的代碼。
首先在Swift中絮蒿,值類(lèi)型優(yōu)先于類(lèi)尊搬。然而,面向?qū)ο蟮母拍畈荒芎芎玫嘏c結(jié)構(gòu)體和枚舉一起工作: 因?yàn)榻Y(jié)構(gòu)體和枚舉不能夠被繼承土涝。
再者,實(shí)際開(kāi)發(fā)工程中,我們經(jīng)常會(huì)遇到如下場(chǎng)景: 假設(shè)我們有一個(gè)ViewController佛寿,它繼承自UIViewController,我們向其新添加一個(gè)方法 customMethod:
class ViewController: UIViewController {
//新添加
func customMethod() {
}
}
這個(gè)時(shí)候我們有另外一個(gè)繼承自UITableViewController的OtherViewController,同樣也需要向其添加方法customMethod
class OtherViewController: UITableViewController {
//新添加
func customMethod() {
}
}
這里就存在一個(gè)問(wèn)題:很難在不同繼承關(guān)系的類(lèi)里共用代碼但壮。
我們的關(guān)注點(diǎn)customMethod位于兩條繼承鏈 UIViewController -> ViewCotroller 和 UIViewController -> UITableViewController -> AnotherViewController 的橫切面上冀泻。面向?qū)ο笫且环N不錯(cuò)的抽象方式,但是肯定不是最好的方式蜡饵。它無(wú)法描述兩個(gè)不同事物具有某個(gè)相同特性這一點(diǎn)弹渔。在這里,特性的組合要比繼承更貼切事物的本質(zhì)溯祸。
總的來(lái)說(shuō),面向協(xié)議編程(POP) 帶來(lái)的好處如下:
結(jié)構(gòu)體肢专、枚舉等值類(lèi)型也可以使用
以繼承多個(gè)協(xié)議舞肆,彌補(bǔ) swift 中類(lèi)單繼承的不足
增強(qiáng)代碼的可擴(kuò)展性,減少代碼的冗余
讓項(xiàng)目更加組件化博杖,代碼可讀性更高
讓無(wú)需的功能代碼組成一個(gè)功能塊椿胯,更便于單元測(cè)試。
使用pop解決上面的問(wèn)題
protocol ex {
func customMethod();
}
在實(shí)際類(lèi)型遵守這個(gè)協(xié)議:
extension ViewController :ex {
func customMethod() {
//
}
}
extension OtherViewController : ex {
func customMethod() {
//
}
}
上述方式就是復(fù)制粘貼
而協(xié)議的擴(kuò)展是可以在 extension ex 中為 customMethod 添加一個(gè)實(shí)現(xiàn):
extension ex {
func customMethod() {
//
}
}
協(xié)議的特性及使用
協(xié)議擴(kuò)展:
1.提供協(xié)議方法的默認(rèn)實(shí)現(xiàn)和協(xié)議屬性的默認(rèn)值剃根,從而使它們成為可選哩盲;符合協(xié)議的類(lèi)型可以提供自己的實(shí)現(xiàn),也可以使用默認(rèn)的實(shí)現(xiàn)狈醉。
2.添加協(xié)議中未聲明的附加方法實(shí)現(xiàn)廉油,并且實(shí)現(xiàn)協(xié)議的任何類(lèi)型都可以使用到這些附加方法。這樣就可以給遵循協(xié)議的類(lèi)型添加特定的方法
protocol Entity {
var name: String {get set}
static func uid() -> String
}
extension Entity {
static func uid() -> String {
return UUID().uuidString
}
}
struct Order: Entity {
var name: String
let uid: String = Order.uid()
}
let order = Order(name: "My Order")
print(order.uid)
協(xié)議繼承
協(xié)議可以從其他協(xié)議繼承苗傅,然后在它繼承的需求之上添加功能抒线,因此可以提供更細(xì)粒度和更靈活的設(shè)計(jì)。
protocol Persistable: Entity {
func write(instance: Entity, to filePath: String)
init?(by uid: String)
}
struct InMemoryEntity: Entity {
var name: String
}
struct PersistableEntity: Persistable {
var name: String
func write(instance: Entity, to filePath: String) { // ...
}
init?(by uid: String) {
// try to load from the filesystem based on id
}
}
協(xié)議的組合
類(lèi)金吗、結(jié)構(gòu)體和枚舉可以符合多個(gè)協(xié)議十兢,它們可以采用多個(gè)協(xié)議的默認(rèn)實(shí)現(xiàn)。這在概念上類(lèi)似于多繼承摇庙。這種組合的方式不僅比將所有需要的功能壓縮到一個(gè)基類(lèi)中更靈活旱物,而且也適用于值類(lèi)型。
struct MyEntity: Entity, Equatable, CustomStringConvertible {
var name: String
// Equatable
public static func ==(lhs: MyEntity, rhs: MyEntity) -> Bool {
return lhs.name == rhs.name
}
// CustomStringConvertible
public var description: String {
return "MyEntity: \(name)"
}
}
let entity1 = MyEntity(name: "42")
print(entity1)
let entity2 = MyEntity(name: "42")
assert(entity1 == entity2, "Entities shall be equal")
網(wǎng)絡(luò)請(qǐng)求
iOS開(kāi)發(fā)中卫袒,一般App端網(wǎng)絡(luò)請(qǐng)求都是通過(guò)一個(gè)API請(qǐng)求到JSON數(shù)據(jù)宵呛,自己轉(zhuǎn)化為model然后刷新UI。
這里用一個(gè)騰訊地圖的API來(lái)獲取數(shù)據(jù)(主要他們數(shù)據(jù)規(guī)范夕凝,還一直可以使用)API 和參數(shù)拼接到一起是
https://apis.map.qq.com/ws/place/v1/suggestion/?region=北京&keyword=美食&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77
這個(gè)API返回的數(shù)據(jù)是
{
"status":0,
"message":"query ok",
"count":100,
"data":[{
"id":"4062879599476274838",
"title":"海底撈火鍋(西單店)",
"address":"北京市西城區(qū)西單北大街109號(hào)西單婚慶大樓7層",
"category":"美食:火鍋",
"type":0,
"location":{
"lat":39.9139,
"lng":116.3732
},
"adcode":110102,
"province":"北京市",
"city":"北京市",
"district":"西城區(qū)"
},
...
],
"request_id":"1053741451029820730"
}
根據(jù)API返回的數(shù)據(jù)類(lèi)型創(chuàng)建數(shù)據(jù)模型RestanurantTotal
import Foundation
struct RestanurantTotal {
let status:Int
let message:String
var data:Array<Any>
let count:Int
let request_id:String
init?(data: Any) {
guard let total = data as? Dictionary<String, Any> else {
return nil
}
guard let status = total["status"] as? Int else {
return nil
}
self.status = status
guard let message = total["message"] as? String else {
return nil
}
self.message = message
guard let count = total["count"] as? Int else {
return nil
}
self.count = count
guard let request_id = total["request_id"] as? String else {
return nil
}
self.request_id = request_id
guard let restaurant = total["data"] as? Array<Dictionary<String, Any>> else {
return nil
}
let array:NSMutableArray = NSMutableArray.init()
for index in 0..<restaurant.count {
let dic = restaurant[index]
let res : Restaurant = Restaurant.init(info: dic)!
array.add(res)
}
self.data = array as! Array<Any>
}
}
struct Restaurant {
let id : String
let title: String
let address: String
let category: String
let type: Int
let location: LLocation
let adcode: Int
let province: String
let district: String
init?(info: Any) {
guard let restaurant = info as? Dictionary<String, Any> else {
return nil
}
guard let id = restaurant["id"] as? String else {
return nil
}
guard let title = restaurant["title"] as? String else {
return nil
}
guard let address = restaurant["address"] as? String else {
return nil
}
guard let category = restaurant["category"] as? String else {
return nil
}
guard let type = restaurant["type"] as? Int else {
return nil
}
guard let adcode = restaurant["adcode"] as? Int else {
return nil
}
guard let province = restaurant["province"] as? String else {
return nil
}
guard let district = restaurant["district"] as? String else {
return nil
}
guard let location = restaurant["location"] as? Dictionary<String, Any> else {
return nil
}
self.id = id
self.title = title
self.address = address
self.category = category
self.type = type
self.adcode = adcode
self.province = province
self.district = district
self.location = LLocation.init(data: (location ))!
}
}
struct LLocation {
let lat: Double
let lng: Double
init?(data: Any) {
guard let location = data as? Dictionary<String, Any> else {
return nil
}
guard let lat = location["lat"] as? Double else {
return nil
}
guard let lng = location["lng"] as? Double else {
return nil
}
self.lat = lat
self.lng = lng
}
}
init中傳入一個(gè)NSDictionary,創(chuàng)建一個(gè)RestanurantTotal實(shí)例宝穗。
如何使用POP的方式從URL請(qǐng)求到數(shù)據(jù)并生成對(duì)應(yīng)的RestanurantTotal,是這里的重點(diǎn)。
我們知道Request是網(wǎng)絡(luò)請(qǐng)求的入口,所以可以直接創(chuàng)建一個(gè)網(wǎng)絡(luò)請(qǐng)求協(xié)議,網(wǎng)絡(luò)請(qǐng)求需要知道路徑,方法,參數(shù)等等码秉。
enum HPHTTPMethod {
case GET
case POST
}
protocol HPRequest {
var host : String {get}
var path : String {get}
var method : HPHTTPMethod {get}
var parameter: [String: Any] { get }
}
請(qǐng)求地址由host和path拼接而成
method支持GET和POST,本例使用GET
parameter是請(qǐng)求的參數(shù)
struct TencentRequest: HPRequest {
typealias Response = RestanurantTotal
var host: String {
return "https://apis.map.qq.com/ws/place/v1/"
// ?region=北京&keyword=美食&key=OB4BZ-D4W3U-B7VVO-4PJWW-6TKDJ-WPB77
}
var path: String {
return "suggestion/"
}
let method: HPHTTPMethod = .GET
var parameter: [String : Any]
}
設(shè)置host路徑和path路徑
指定method為GET
這個(gè)時(shí)候,我們已經(jīng)有了發(fā)請(qǐng)求的條件(路由,方法,參數(shù))逮矛。下一步就需要發(fā)送請(qǐng)求了。我們可以為HPRequest擴(kuò)展發(fā)送請(qǐng)求的能力,這樣可以讓每一個(gè)請(qǐng)求都是用一樣的方法發(fā)送的转砖。
extension HPRequest {
func sendRequest(hander:@escaping(RestanurantTotal)->Void) {
//...
}}
為HPRequest擴(kuò)展sendRequest
逃逸閉包hander可將請(qǐng)求結(jié)果返回到外界
這里返回的是RestanurantTotal模型,這樣的話(huà)sendRequest方法就只能支持這個(gè)餐館請(qǐng)求须鼎。我們可以使用關(guān)聯(lián)類(lèi)型解決這個(gè)問(wèn)題,使請(qǐng)求一般化.
protocol HPRequest {
//.....
associatedtype Response
}
struct RencentRequest: JNRequest {
typealias Response = RestanurantTotal
//....
}
extension HPRequest {
func sendRequest(hander:@escaping(Response)->Void) {
//...
}
}
HPRequest協(xié)議中添加associatedtype Response
TencentRequest添加typealias Response = RestanurantTotal,執(zhí)行返回類(lèi)型為RestanurantTotal
HPRequest的擴(kuò)展方法sendRequest的逃逸閉包將返回類(lèi)型改為Response
sendRequest發(fā)送方法中,使用Alamofire發(fā)送網(wǎng)絡(luò)請(qǐng)求
let url = self.host + self.path
Alamofire.request(url, method: HTTPMethod.get, parameters: self.parameter).responseJSON { (response) in
switch response.result {
print(result)
}
}
拼接url
調(diào)用Alamofire.request請(qǐng)求數(shù)據(jù)
使用JSON序列化器
獲取到網(wǎng)絡(luò)返回的JSON數(shù)據(jù)
現(xiàn)在還差最后一步,將返回的JSON數(shù)據(jù)轉(zhuǎn)化為RestanurantTotal模型數(shù)據(jù) 我們?yōu)镠PRequest協(xié)議添加方法
func parse(data: NSDictionary) -> Response?
TencentRequest的擴(kuò)展中實(shí)現(xiàn)parse
extension TencentRequest {
func parse(data: NSDictionary) -> RestanurantTotal? {
return RestanurantTotal(data:data)
}
}
sendRequest中調(diào)用序列化解析
func sendRequest(hander:@escaping(Response?)->Void) {
//...
let url = self.host + self.path
Alamofire.request(url, method: HTTPMethod.get, parameters: self.parameter).responseJSON { (response) in
switch response.result {
case .success(let data):
let dic = data as? NSDictionary
if let res = self.parse(data: dic!) {
hander(res)
}else {
hander(nil)
}
case .failure:
hander(nil)
}
}
}
調(diào)用
let request = TencentRequest(parameter: ["region":"北京","keyword":"美食","key":"YSJBZ-E7KWX-KJM4K-7Z6S7-TREBF-ILBJG"])
request.sendRequest { (Total) in
let total:RestanurantTotal = (Total as RestanurantTotal?)!
let resta:Restaurant = total.data.first! as! Restaurant
print("第一個(gè)地區(qū)的id是\(resta.id)")
}
使用起來(lái)非常便捷,也能實(shí)現(xiàn)需求。但是這樣的實(shí)現(xiàn)非常差勁府蔗〗兀回頭看看HPRequest的定義和擴(kuò)展:
protocol HPRequest {
var host : String {get}
var path : String {get}
var method : HPHTTPMethod {get}
var parameter: [String: Any] { get }
associatedtype Response
func parse(data: NSDictionary) -> Response?
}
extension HPRequest {
func sendRequest(hander:@escaping(Response?)->Void) {
...
}
上面的實(shí)現(xiàn)主要問(wèn)題在于Request管理的東西太多.Request該做的事情應(yīng)該是定義請(qǐng)求入口,保存請(qǐng)求的信息和響應(yīng)類(lèi)型。而這里的Request保存host,還進(jìn)行數(shù)據(jù)的解析成,這樣做就無(wú)法在不修改請(qǐng)求的情況下更改解析的方式,增加耦合度,不利于測(cè)試姓赤。發(fā)送請(qǐng)求也是它的一部分,這樣請(qǐng)求的具體實(shí)現(xiàn)就和請(qǐng)求產(chǎn)生耦合,這也是不合理的...
重構(gòu)優(yōu)化
鑒于上述問(wèn)題赡译,開(kāi)始優(yōu)化代碼,先將send從Request中剝離出來(lái)不铆。因此需要一個(gè)單獨(dú)的類(lèi)型負(fù)責(zé)發(fā)送請(qǐng)求蝌焚。根據(jù)POP協(xié)議裹唆,定義以下協(xié)議
protocol LHDataClient {
var host: String { get }
func send<T :LHRequest>(_ r : T, handler: @escaping(T.Response?)->Void)
}
host不應(yīng)該在Request中設(shè)置,我們將其移動(dòng)到LHDataClient只洒。清除請(qǐng)求中的host以及send品腹。并定義LHAlamofireClient實(shí)現(xiàn)LHDataClient協(xié)議:
struct LHAlamofireClient {
static let `default` = LHAlamofireClient()
var host: String {
return "https://apis.map.qq.com/ws/place/v1/"
}
func send<T :LHRequest>(_ r : T, handler: @escaping(T.Response?)->Void) {
let url = self.host + r.path
Alamofire.request(url, method:HTTPMethod.get, parameters: r.parameter).responseJSON { (response) in
switch response.result {
case .success(let data):
if let dic = data as? NSDictionary {
if let res = T.Response.parse(data: dic) {
handler(res)
}else {
handler(nil)
}
}else {
handler(nil)
}
case .failure:
handler(nil)
}
}
}
}
目前已經(jīng)將發(fā)送請(qǐng)求和請(qǐng)求本身分離開(kāi),我們定義了LHDataClient協(xié)議红碑,這里實(shí)現(xiàn)了LHAlamofireClient,使用Alamofire發(fā)送請(qǐng)求泡垃。對(duì)象的解析不應(yīng)該由Request來(lái)完成析珊,交給應(yīng)該Response我們新增一個(gè)協(xié)議,滿(mǎn)足這個(gè)協(xié)議的需要實(shí)現(xiàn)蔑穴。parse方法:
protocol Decodable {
static func parse(data : NSDictionary)->Self?
}
為了保證所有的Response都能解析數(shù)據(jù)忠寻,我們需要對(duì)Response實(shí)現(xiàn)Decodable協(xié)議,并刪除Request的解析方法
protocol JNRequest {
var path : String {get}
var httpMethod : JNHTTPMethod {get}
var parameter: [String: Any] { get }
associatedtype Response : Decodable
}
為Model類(lèi)擴(kuò)展協(xié)議方法
extension RestanurantTotal : Decodable {
static func parse(data: NSDictionary) -> RestanurantTotal? {
return RestanurantTotal(data: data)!
}
}
send中直接提交T.response:
if let dic = data as? NSDictionary {
if let res = T.Response.parse(data: dic) {
handler(res)
}else {
handler(nil)
}
}else {
handler(nil)
}
創(chuàng)建單例
static let `default` = LHAlamofireClient()
外部調(diào)用
let request = LHlhRequest(parameter: ["region":"北京","keyword":"美食","key":"JBZ-E7KWX-KJM4K-7Z6S7-TREBF-ILBJG"])
LHAlamofireClient.default.send(request) {(Total) in
print(Total!)
let total:RestanurantTotal = (Total as RestanurantTotal?)!
let resta:Restaurant = total.data.first! as! Restaurant
print("第一個(gè)地區(qū)的id是\(resta.id)")
}
})
如果需要?jiǎng)?chuàng)建其他的請(qǐng)求存和,可以使用和LHlhRequest相似的方式奕剃,為網(wǎng)絡(luò)層添加其他的API請(qǐng)求,只需要定義請(qǐng)求所必要的內(nèi)容捐腿,而不用擔(dān)心會(huì)觸及網(wǎng)絡(luò)方面的具體實(shí)現(xiàn)纵朋。
易于測(cè)試
準(zhǔn)備一個(gè)response.json的文件,內(nèi)容是
{
"status":0,
"message":"query ok",
"count":100,
"data":[{
"id":"4412846406955100612",
"title":"鮮魚(yú)口老字號(hào)美食街",
"address":"北京市東城區(qū)前門(mén)東路附近",
"category":"美食:中餐廳:其它中餐廳",
"type":0,
"location":{
"lat":39.89605,
"lng":116.39991
},
"adcode":110101,
"province":"北京市",
"city":"北京市",
"district":"東城區(qū)"
},
{
"id":"12500720047859529446",
"title":"初色海鮮自助火鍋",
"address":"北京市豐臺(tái)區(qū)萬(wàn)豐路302號(hào)",
"category":"美食:海鮮",
"type":0,
"location":{
"lat":39.870803,
"lng":116.293982
},
"adcode":110106,
"province":"北京市",
"city":"北京市",
"district":"豐臺(tái)區(qū)"
},
{
"id":"8301295427127788926",
"title":"王府井小吃街",
"address":"北京市東城區(qū)王府井大街與大紗帽胡同交叉口西北角",
"category":"美食:小吃快餐",
"type":0,
"location":{
"lat":39.910895,
"lng":116.410904
},
"adcode":110101,
"province":"北京市",
"city":"北京市",
"district":"東城區(qū)"
},
{
"id":"11385228330756553756",
"title":"新干線(xiàn)美食一條街",
"address":"北京市朝陽(yáng)區(qū)霄云路35號(hào)三元橋",
"category":"美食:中餐廳:其它中餐廳",
"type":0,
"location":{
"lat":39.957371,
"lng":116.460884
},
"adcode":110105,
"province":"北京市",
"city":"北京市",
"district":"朝陽(yáng)區(qū)"
},
{
"id":"2151925127924188045",
"title":"鼎好美食廣場(chǎng)",
"address":"北京市海淀區(qū)中關(guān)村日月光·鼎好大廈B座",
"category":"美食:小吃快餐",
"type":0,
"location":{
"lat":39.983855,
"lng":116.314364
},
"adcode":110108,
"province":"北京市",
"city":"北京市",
"district":"海淀區(qū)"
},
{
"id":"6120575816560518252",
"title":"全聚德烤鴨店(天安門(mén)店)",
"address":"北京市東城區(qū)東交民巷44號(hào)",
"category":"美食:其它美食",
"type":0,
"location":{
"lat":39.90147,
"lng":116.40007
},
"adcode":110101,
"province":"北京市",
"city":"北京市",
"district":"東城區(qū)"
},
{
"id":"16827131455368440528",
"title":"上可味美食廣場(chǎng)",
"address":"北京市豐臺(tái)區(qū)南四環(huán)西路188號(hào)",
"category":"美食:小吃快餐",
"type":0,
"location":{
"lat":39.824105,
"lng":116.283744
},
"adcode":110106,
"province":"北京市",
"city":"北京市",
"district":"豐臺(tái)區(qū)"
},
{
"id":"12511480423602913915",
"title":"可味美食城(三里屯店)",
"address":"北京市朝陽(yáng)區(qū)工人體育場(chǎng)北路8號(hào)院三里屯SOHO6號(hào)商場(chǎng)B1層",
"category":"美食:小吃快餐",
"type":0,
"location":{
"lat":39.93167,
"lng":116.45363
},
"adcode":110105,
"province":"北京市",
"city":"北京市",
"district":"朝陽(yáng)區(qū)"
},
{
"id":"8119722848339667861",
"title":"美食廣場(chǎng)食字街區(qū)(北京市百貨大樓)",
"address":"北京市東城區(qū)王府井大街255號(hào)北京市百貨大樓F7",
"category":"美食:小吃快餐",
"type":0,
"location":{
"lat":39.914084001,
"lng":116.410407448
},
"adcode":110101,
"province":"北京市",
"city":"北京市",
"district":"東城區(qū)"
},
{
"id":"7632255329310100195",
"title":"西單明珠市場(chǎng)美食廣場(chǎng)",
"address":"北京市西城區(qū)橫二條59號(hào)西單明珠市場(chǎng)8層",
"category":"美食:中餐廳:其它中餐廳",
"type":0,
"location":{
"lat":39.9103,
"lng":116.37618
},
"adcode":110102,
"province":"北京市",
"city":"北京市",
"district":"西城區(qū)"
}],
"request_id":"658616875259013526"
}
步驟,創(chuàng)建一個(gè)類(lèi)型LHLocalClient,實(shí)現(xiàn)LHDataClient協(xié)議:
struct LHLocalClient {
var host: String {
return ""
}
func send<T :LHRequest>(_ r : T, handler: @escaping(T.Response?)->Void) {
switch r.path {
case "suggestion/":
let fileURL = Bundle.main.path(forResource: "response", ofType: "json")
// if let data = try? String.init(contentsOfFile: fileURL!, encoding: .utf8) {
// let jsonData:Data = data.data(using: .utf8)!
// if let dic = try? JSONSerialization.jsonObject(with: jsonData, options: .mutableContainers) {
// if let res = T.Response.parse(data: dic as! NSDictionary) {
// handler(res)
// }else {
// handler(nil)
// }
// }else {
// handler(nil)
// }
// }
if let data = try? String.init(contentsOfFile: fileURL!, encoding: .utf8) {
let jsonData:Data = data.data(using: .utf8)!
if let res = T.Response.parse(data: jsonData) {
handler(res)
}else {
handler(nil)
}
}else
{
handler(nil)
}
default:
handler(nil)
}
}
}
檢查輸入請(qǐng)求的path屬性茄袖,根據(jù)path不同,從bundle中讀取預(yù)先設(shè)定的文件數(shù)據(jù)操软。
對(duì)返回的結(jié)果做JSON解析,然后調(diào)用Response的parse解析
調(diào)用handler返回?cái)?shù)據(jù)到外界。
如果我們需要增加其他請(qǐng)求的測(cè)試宪祥,可以添加新的case項(xiàng)
protocol Decodable {
static func parse(data : NSDictionary)->Self?
}
所以在LHLocalClient中我們需要自己解析成JSON,使用起來(lái)不太好用聂薪。使用POP的方式我們可以不限定單獨(dú)的類(lèi)型,而是限定一個(gè)協(xié)議DecodeType:
protocol DecodeType {
func asDictionary() -> NSDictionary?;
}
可能傳入解析的類(lèi)型比如NSDictionary,Data等添加擴(kuò)展:
extension NSDictionary : DecodeType {
func asDictionary() -> NSDictionary? {
return self
}
}
extension Data : DecodeType {
func asDictionary() -> NSDictionary? {
if let dic = try? JSONSerialization.jsonObject(with: self, options: .mutableContainers) {
return dic as? NSDictionary
}
return nil
}
}
修改協(xié)議Decodable協(xié)議,限定參數(shù)類(lèi)型DecodeType協(xié)議:
protocol Decodable {
static func parse(data : DecodeType)->Self?
}
修改RestanurantTotal的解析方式:
extension RestanurantTotal : Decodable {
static func parse(data: DecodeType) -> RestanurantTotal? {
return RestanurantTotal(data: data.asDictionary()!)
}
}
LHLocalClient的send方法不需要在解析JSON,直接調(diào)用Parse解析:
if let data = try? String.init(contentsOfFile: fileURL!, encoding: .utf8) {
let jsonData:Data = data.data(using: .utf8)!
if let res = T.Response.parse(data: jsonData) {
handler(res)
}else {
handler(nil)
}
}else {
handler(nil)
}
這樣用起來(lái)就更方便了蝗羊。 回到剛才的話(huà)題,有了LHLocalClient,我們就可以不受網(wǎng)絡(luò)的限制,單獨(dú)測(cè)試LoginRequest藏澳、 parse是否正常,以及之后的各個(gè)流程是否正常。
解耦&可擴(kuò)展
基于POP實(shí)現(xiàn)的代碼高度解耦,為代碼的擴(kuò)展提供相對(duì)寬松的可能性耀找。在上面的例子中,我們可以?xún)H僅實(shí)現(xiàn)發(fā)送請(qǐng)求的方法,在不影響請(qǐng)求定義和使用的情況下更換了請(qǐng)求方式翔悠。這里我們使用手動(dòng)解析賦值模型,我們我們完全可以使用第三方解析庫(kù),HandyJSON涯呻,來(lái)幫助我們迅速構(gòu)建模型類(lèi)型凉驻。
pod里面添加代碼
pod 'HandyJSON'
步驟參考:https://juejin.im/post/5d6a1bad518825391623e64b
代碼拉取地址:https://gitee.com/xgkp/SwiftNetRequest.git