網絡層這一塊用Alamofire,如同于在oc中用AFNetworking.但是,如果你直接使用的話,會使得各種網絡請求操作分布很淩亂,是以我選擇了巧神封裝的YTKNetwork,很好用,有興趣的可以看一下.當然你也可以自己組織封裝.
這段代碼就是LZ項目中的網絡請求:
NSDictionary *parameterDic = @{kPageSizeKey:@"10",kCurPageKey:@"1",kLastIDKey:@"0"};
[[WCRequestDataManager sharedRequestDataManager] requestDataForNetWorkWithDataHandleType:WCProductListDataHandleType
parameterDic:parameterDic
completed:^(WCProductResultModel *resultModel) {}
failure:^(NSString *msg) {}
];
-
就是請求所需的參數,如果沒有直接傳入parameterDic
nil
-
是枚舉類型,你可以了解為它對應了WCProductListDataHandleType
網絡請求的産品清單
,method(GET/POST)
等等URL
-
和completed
2個block分别對應請求failure
和成功
兩種情況,并傳回頁面需要的model和失敗的資訊失敗
- 資料解析直接在對應的
中,保證傳回對應的model->RequestHandle
WCProductResultModel
那麼Swift中推薦一下Moya,這是一個基于
Alamofire
的更高層網絡請求封裝抽象層.
整個Demo可以在這裡下載下傳到:MoyaTest
可以對比一下直接用
Alamofire
和用
Moya
請求樣式:
Alamofire.request(.GET, kRequestServerKey + "services/creditor/product/list/page/2/0/0").responseJSON {
response in
if let value = response.result.value {
let result = Mapper<CommonInfo>().map(value)
let dataList = Mapper<ProductModel>().mapArray(result?.data?["result"])
print("Alamofire = \(dataList?[0].productDesc)") // Alamofire = Optional("gfhgfgfhgshgdsfdshgfshfgh")
}
}
MoyaTest.sharedInstance.requestDataWithTarget(.productList(pageSize: , curpage: , lastID: ), type: ProductModel.self, successClosure: { result in
let dataList = Mapper<ProductModel>().mapArray(result["result"])
print("Moya = \(dataList?[0].productDesc)") // Moya = Optional("gfhgfgfhgshgdsfdshgfshfgh")
}) { errorMsg in
print(errorMsg)
}
可見,第二種隐藏了
url
,
method
,
json解析
等參數/操作,抽象出了一層通用的請求方法.(按理說
Mapper<ProductModel>().mapArray(result["result"])
不應該出現在回調的閉包中,傳回的就應該是
productList
請求對應的model,否則
type
這個參數就沒有意義了,這個梗會在下面說到)
看一下文檔說明:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIn5GcugjMuMzMuAjMwITJ2ETLyETL1EDMyAjMlcahnv6vlXZul-Ysl-CXt92Yu4GZkV3bsNmLix2ZuAjeuETbvNmLup3NuF3Nvw1LcpDc0RHaiojIsJye.png)
Targets
使用
Moya
的第一步就是定義一個
Target
:通常是指一些符合
TargetType protocol
的
enum
.然,你請求的其餘部分都隻根據這個
Target
而來.這個枚舉用來定義你的
網絡請求API
的行為
action
.
“`
public enum RequestApi {
// UserApi
case login(loginName: String, password: String)
case register //(userMobile: String, password: String, inviteCode: String, verifyCode: String)
//case accountInfo
// ProductApi
case productList(pageSize: Int, curpage: Int, lastID: Int)
// case productDetail(id: Int)
}
+ 強烈推薦[Swift 中枚舉進階用法及實踐](http://swift.gg/2015/11/20/advanced-practical-enum-examples/)這篇文章,涵蓋了枚舉幾乎所有的知識點.`enum`在Swift中的作用,簡直不要太牛!
+ 再推薦一個[用模式比對解析 URL](http://swift.gg/2015/09/15/urls-and-pattern-matching/#qrcode),通過`關聯值(Associated Value)`來定義請求所需的參數(loginName和password也可以省略掉,但為了直覺的說明,還是保留一下)
extension RequestApi: TargetType {
public var baseURL: NSURL {
return NSURL(string: “http://apptest.wecube.com:8080/taojinjia/“)!
}
public var path: String {
switch self {
case .login(_,_):
return "services/crane/sso/login/doLogin"
case .register:
return "services/crane/sso/login/register"
case let .productList(pageSize, curpage, lastID):
return "services/creditor/product/list/page/"+String(pageSize)+"/"+String(curpage)+"/"+String(lastID)
}
}
public var method: Moya.Method {
switch self {
case .login(_,_), .register:
return .POST
case .productList(_,_,_):
return .GET
}
}
public var parameters: [String: AnyObject]? {
switch self {
case let .login(loginName, password):
return ["loginName": loginName, "userPassword": password]
default :
return nil
}
}
// 單元測試用
public var sampleData: NSData {
return "{}".dataUsingEncoding(NSUTF8StringEncoding)!
}
}
定義的enum實作`TargetType`協定,完成一系列初始化設定:
+ <font color="IndianRed">`baseURL`</font>:統一設定伺服器位址,測試切換非常的友善,`YTKNetwork`中也是這樣配置的.
+ <font color="IndianRed">`path`</font>:每個請求需求對應的各自的請求路徑
參見源碼,最終的url就是由baseURL和path拼接而來
public final class func DefaultEndpointMapping(target: Target) -> Endpoint {
let url = target.baseURL.URLByAppendingPathComponent(target.path).absoluteString
return Endpoint(URL: url, sampleResponseClosure: {.NetworkResponse(200, target.sampleData)}, method: target.method, parameters: target.parameters)
}
``
+ <font color="IndianRed">
method
</font>:不解釋...請求方式
+ <font color="IndianRed">
parameters
</font>:需要的參數
+ <font color="IndianRed">
sampleData`:友善于單元測試…暫時忽略
Providers和Endpoints
provider
和
endpoints
是緊密相關的,放在一起講更好點(名字都怪怪的,果然國外開發者取名都是講究哇)
let requestProvider = RxMoyaProvider<RequestApi>()
最終的請求發起對象就是
requestProvider
,
RxMoyaProvider
是
MoyaProvider
的子類,你需要在podfile中導入
Moya/RxSwift
,當然你也可以直接用
MoyaProvider
來完成初始化,
RxSwift
目前隻是簡單的了解了一下,具體用法這裡暫時忽略,不影響請求的完成.
你可能發現,這跟
endpoints
并沒什麼關系,但是,看下源碼:
/// Initializes a provider.
public init(endpointClosure: EndpointClosure = MoyaProvider.DefaultEndpointMapping,
requestClosure: RequestClosure = MoyaProvider.DefaultRequestMapping,
stubClosure: StubClosure = MoyaProvider.NeverStub,
manager: Manager = Alamofire.Manager.sharedInstance,
plugins: [PluginType] = []) {
self.endpointClosure = endpointClosure
self.requestClosure = requestClosure
self.stubClosure = stubClosure
self.manager = manager
self.plugins = plugins
}
/// Mark: Defaults
public extension MoyaProvider {
// These functions are default mappings to endpoings and requests.
public final class func DefaultEndpointMapping(target: Target) -> Endpoint<Target> {
let url = target.baseURL.URLByAppendingPathComponent(target.path).absoluteString
return Endpoint(URL: url, sampleResponseClosure: {.NetworkResponse(, target.sampleData)}, method: target.method, parameters: target.parameters)
}
public final class func DefaultRequestMapping(endpoint: Endpoint<Target>, closure: NSURLRequest -> Void) {
return closure(endpoint.urlRequest)
}
}
-
的4個參數都給了預設參數,且預設的endpointinit
如同它的名字一樣,”終結點”比對了網絡請求要的因素.DefaultEndpointMapping
- 如果你的請求需要添加請求頭,你也能夠通過
方法來實作.endpointByAddingHTTPHeaderFields
-
貫穿了全局,在endpoint的配置中,也可以通過刷選不同的枚舉值來設定不同情況.Target
-
還有一些進階用法就自己去研究文檔,LZ的英文實在是渣的可怕…
在上面的栗子中,選擇了預設的初始化方法.
Request
import Foundation
import Moya
import RxSwift
import ObjectMapper
import SwiftyJSON
typealias SuccessClosure = (result: AnyObject) -> Void
//typealias SuccessClosure = (result: Mappable) -> Void
typealias FailClosure = (errorMsg: String?) -> Void
enum RequestCode: String {
case failError = "0"
case success = "1"
}
class MoyaTest {
static let sharedInstance = MoyaTest()
private init(){}
let requestProvider = RxMoyaProvider<RequestApi>()
func requestDataWithTarget<T: Mappable>(target: RequestApi, type: T.Type , successClosure: SuccessClosure, failClosure: FailClosure) {
let _ = requestProvider.request(target).subscribe { (event) -> Void in
switch event {
case .Next(let response):
let info = Mapper<CommonInfo>().map(JSON(data: response.data,options: .AllowFragments).object)
guard info?.code == RequestCode.success.rawValue else {
failClosure(errorMsg: info?.msg)
return
}
guard let data = info?.data else {
failClosure(errorMsg: "資料為空")
return
}
successClosure(result: data)
case .Error(let error):
print("網絡請求失敗...\(error)")
default:
break
}
}
}
}
最後的請求方法封裝,如上面的栗子:
+ json的解析我用的
SwiftyJson
和
ObjectMapper
.
SwiftyJson
主要是用來把data轉為object(這裡如果調用
JSON(response.data)
會無法解析,要顯式的加上參數
options
,但其實
JSON(xxx)
内部是預設實作了的,實在不明白為什麼會解析失敗…參數的解釋參見hit me和hit me too),後面的轉model用的就是
ObjectMapper
.這裡補上前面提到的:為什麼沒能夠做到傳回直接是請求資料對應的model,而多做了一步
let dataList = Mapper<ProductModel>().mapArray(result["result"])
// 伺服器給的資料格式統一為
{
"code" = "",
"data" = {} 或 ({}),
"msg" = ""
}
data
對應的就是請求url傳回的
model
或
[model]
,那麼就是不是調用
successClosure(result: data)
了,而是
//typealias SuccessClosure = (result: Mappable) -> Void
let model = Mapper<T>().map(data)
successClosure(result: model)
有的接口
data
對應的是包含了多個dic的數組,感覺解決方法就是再單獨開一個數組的請求方法,調用
mapArray
,這裡就不多加描述了,反正都一樣的流程.
而
productList
的url傳回的
data
裡面還包了一層
result
和
pageVO
,so…這就是一個特殊情況^_^!
+
RxSwift
…學習中
ok!差不多
Moya
的基本使用就是這樣啦,感覺還是非常友善實用的.
參考資料
通過 Moya+RxSwift+Argo 完成網絡請求
RxSwift