天天看點

Swift Moya

網絡層這一塊用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

    failure

    2個block分别對應請求

    成功

    失敗

    兩種情況,并傳回頁面需要的model和失敗的資訊
  • 資料解析直接在對應的

    RequestHandle

    中,保證傳回對應的model->

    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

這個參數就沒有意義了,這個梗會在下面說到)

看一下文檔說明:

Swift Moya

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)
    }
}
           
  • init

    的4個參數都給了預設參數,且預設的endpoint

    DefaultEndpointMapping

    如同它的名字一樣,”終結點”比對了網絡請求要的因素.
  • 如果你的請求需要添加請求頭,你也能夠通過

    endpointByAddingHTTPHeaderFields

    方法來實作.
  • Target

    貫穿了全局,在endpoint的配置中,也可以通過刷選不同的枚舉值來設定不同情況.
  • 還有一些進階用法就自己去研究文檔,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