網(wǎng)絡(luò)層這一塊用Alamofire,如同于在oc中用AFNetworking.但是,如果你直接使用的話,會使得各種網(wǎng)絡(luò)請求操作分布很凌亂,所以我選擇了巧神封裝的YTKNetwork,很好用,有興趣的可以看一下.當然你也可以自己組織封裝.
這段代碼就是LZ項目中的網(wǎng)絡(luò)請求:
NSDictionary *parameterDic = @{kPageSizeKey:@"10",kCurPageKey:@"1",kLastIDKey:@"0"};
[[WCRequestDataManager sharedRequestDataManager] requestDataForNetWorkWithDataHandleType:WCProductListDataHandleType
parameterDic:parameterDic
completed:^(WCProductResultModel *resultModel) {}
failure:^(NSString *msg) {}
];
-
parameterDic
就是請求所需的參數(shù),如果沒有直接傳入nil
-
WCProductListDataHandleType
是枚舉類型,你可以理解為它對應(yīng)了產(chǎn)品列表
網(wǎng)絡(luò)請求的method(GET/POST)
,URL
等等 -
completed
和failure
2個block分別對應(yīng)請求成功
和失敗
兩種情況,并返回頁面需要的model和失敗的信息 - 數(shù)據(jù)解析直接在對應(yīng)的
RequestHandle
中,保證返回對應(yīng)的model->WCProductResultModel
那么Swift中推薦一下Moya,這是一個基于Alamofire
的更高層網(wǎng)絡(luò)請求封裝抽象層.
整個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: 2, curpage: 0, lastID: 0), 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解析
等參數(shù)/操作,抽象出了一層通用的請求方法.(按理說Mapper<ProductModel>().mapArray(result["result"])
不應(yīng)該出現(xiàn)在回調(diào)的閉包中,返回的就應(yīng)該是productList
請求對應(yīng)的model,否則type
這個參數(shù)就沒有意義了,這個梗會在下面說到)
看一下文檔說明:
![](http://7qn7zn.com1.z0.glb.clouddn.com/%E5%B1%8F%E5%B9%95%E5%BF%AB%E7%85%A7%202015-12-16%2020.33.28.png)
<font size="5" color="IndianRed">Targets</font>
使用Moya
的第一步就是定義一個Target
:通常是指一些符合TargetType protocol
的enum
.然,你請求的其余部分都只根據(jù)這個Target
而來.這個枚舉用來定義你的網(wǎng)絡(luò)請求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 中枚舉高級用法及實踐這篇文章,涵蓋了枚舉幾乎所有的知識點.
enum
在Swift中的作用,簡直不要太牛! - 再推薦一個用模式匹配解析 URL,通過
關(guān)聯(lián)值(Associated Value)
來定義請求所需的參數(shù)(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實現(xiàn)TargetType
協(xié)議,完成一系列初始化設(shè)置:
- <font color="IndianRed">
baseURL
</font>:統(tǒng)一設(shè)置服務(wù)器地址,測試切換非常的方便,YTKNetwork
中也是這樣配置的. - <font color="IndianRed">
path
</font>:每個請求需求對應(yīng)的各自的請求路徑
參見源碼,最終的url就是由baseURL和path拼接而來
public final class func DefaultEndpointMapping(target: Target) -> Endpoint<Target> {
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>:需要的參數(shù) - <font color="IndianRed">
sampleData
</font>:方便于單元測試...暫時忽略
<font size="5" color="IndianRed">Providers和Endpoints</font>
provider
和endpoints
是緊密相關(guān)的,放在一起講更好點(名字都怪怪的,果然國外開發(fā)者取名都是講究哇)
let requestProvider = RxMoyaProvider<RequestApi>()
最終的請求發(fā)起對象就是requestProvider
,RxMoyaProvider
是MoyaProvider
的子類,你需要在podfile中導(dǎo)入Moya/RxSwift
,當然你也可以直接用MoyaProvider
來完成初始化,RxSwift
目前只是簡單的了解了一下,具體用法這里暫時忽略,不影響請求的完成.
你可能發(fā)現(xiàn),這跟endpoints
并沒什么關(guān)系,但是,看下源碼:
/// 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(200, target.sampleData)}, method: target.method, parameters: target.parameters)
}
public final class func DefaultRequestMapping(endpoint: Endpoint<Target>, closure: NSURLRequest -> Void) {
return closure(endpoint.urlRequest)
}
}
-
init
的4個參數(shù)都給了默認參數(shù),且默認的endpointDefaultEndpointMapping
如同它的名字一樣,"終結(jié)點"匹配了網(wǎng)絡(luò)請求要的因素. - 如果你的請求需要添加請求頭,你也能夠通過
endpointByAddingHTTPHeaderFields
方法來實現(xiàn). -
Target
貫穿了全局,在endpoint的配置中,也可以通過刷選不同的枚舉值來設(shè)置不同情況. - 還有一些高級用法就自己去研究文檔,LZ的英文實在是渣的可怕...
在上面的栗子中,選擇了默認的初始化方法.
<font size="5" color="IndianRed">Request</font>
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: "數(shù)據(jù)為空")
return
}
successClosure(result: data)
case .Error(let error):
print("網(wǎng)絡(luò)請求失敗...\(error)")
default:
break
}
}
}
}
最后的請求方法封裝,如上面的栗子:
- json的解析我用的
SwiftyJson
和ObjectMapper
.SwiftyJson
主要是用來把data轉(zhuǎn)為object(這里如果調(diào)用JSON(response.data)
會無法解析,要顯式的加上參數(shù)options
,但其實JSON(xxx)
內(nèi)部是默認實現(xiàn)了的,實在不明白為什么會解析失敗...參數(shù)的解釋參見hit me和hit me too),后面的轉(zhuǎn)model用的就是ObjectMapper
.<font color="red">這里補上前面提到的:為什么沒能夠做到返回直接是請求數(shù)據(jù)對應(yīng)的model,而多做了一步let dataList = Mapper<ProductModel>().mapArray(result["result"])
</font>
// 服務(wù)器給的數(shù)據(jù)格式統(tǒng)一為
{
"code" = "",
"data" = {} 或 ({}),
"msg" = ""
}
data
對應(yīng)的就是請求url返回的model
或[model]
,那么就是不是調(diào)用successClosure(result: data)
了,而是
//typealias SuccessClosure = (result: Mappable) -> Void
let model = Mapper<T>().map(data)
successClosure(result: model)
有的接口data
對應(yīng)的是包含了多個dic的數(shù)組,感覺解決方法就是再單獨開一個數(shù)組的請求方法,調(diào)用mapArray
,這里就不多加描述了,反正都一樣的流程.
而productList
的url返回的data
里面還包了一層result
和pageVO
,so...這就是一個特殊情況_!
-
RxSwift
...學習中
ok!差不多Moya
的基本使用就是這樣啦,感覺還是非常方便實用的.