• MoyaPlays an important role in network interaction in Swift development, but there are disadvantages, such as the return when the network is unavailableResponsenilAnd then you have to parse the correspondingError
  • CodableIt can help us parse data quickly, but if the declared property type is different from that in JSON, it will not parse properly. Moreover, the processing of custom attribute names in the model is also very tedious

There are many solutions, but I am used to using MoyaMapper, which can not only solve the above problems, but also provide a variety of convenient methods for model transformation, data exchange, and arbitrary storage of various data types. Controlling Moya’s web requests, data parsing, and caching is a breeze.

MoyaMapper is based on Moya and SwiftyJSON package tool, Moya plugin to achieve indirect parsing, support RxSwift

GitHub: MoyaMapper

📖 please refer to the manual moyamapper.github. IO for details

The characteristics of

  • supportjsonModelAutomatic mapping and custom mapping
  • ignorejsonThe type of the median,ModelWhat type is declared by an attribute in
  • supportData The dictionary JSON Json string ModelTransfers between
  • Plug-in mode, all-round protectionMoya.ResponseReject all kinds of network problems caused byResponsenil, the data loading failure caused by various reasons will be uniformly handled, developers only need to pay attention toResponse
  • Optional – Support arbitrary data caching (JSONNumberString,Bool,Moya.Response )
  • Optional – Network request caching is supported

Data parsing

First, plug-in injection

Attached: plugin MoyaMapperPlugin detailed use

Define ModelableParameterType for the project interface

// statusCodeKey, tipStrKey, modelKey can specify any level of path, such as "error>used"
struct NetParameter : ModelableParameterType {
    var successValue = "000"
    var statusCodeKey = "retStatus"
    var tipStrKey = "retMsg"
    var modelKey = "retBody"
}
Copy the code

Use MoyaMapperPlugin in MoyaProvider and specify ModelableParameterType

let lxfNetTool = MoyaProvider<LXFNetworkTool>(plugins: [MoyaMapperPlugin(NetParameter()))Copy the code

❗ Using MoyaMapperPlugin is the core of the whole MoyaMapper!

Ii. Model Statement

The Model complies with the Modelable protocol

  • MoyaMapperSupport for model automatic mapping and custom mapping
  • Regardless of the actual type of the source JSON data, clickModelTo the type declared by the property in

1. In general, it can be written as follows

struct CompanyModel: Modelable {
    
    var name : String = ""
    var catchPhrase : String = ""
    
    init() {}}Copy the code

Mutating func mapping(_ json: json)

struct CompanyModel: Modelable {
    
    var name : String = ""
    var catchPhrase : String = ""
    
    init() {}mutating func mapping(_ json: JSON) {
        self.name = json["nickname"].stringValue
    }
}
Copy the code

3. Support model nesting

struct UserModel: Modelable {
    
    var id : String = ""
    var name : String = ""
    var company : CompanyModel = CompanyModel(a)init() {}}Copy the code
3

1. The following examples all use MoyaMapperPlugin, so there is no need to specify a resolution path

2. If MoyaMapperPlugin is not used, specify the parsing path; otherwise, the parsing fails

Ps: Path resolution can use the form A > B to solve the multilevel path problem

The following list shows the resolution methods

methods Description (RxSwift support)
toJSON The Response to JSON (toJSON | rx.toJSON)
fetchString Gets the string for the specified path (fetchString | rx.fetchString)
fetchJSONString Gets the raw JSON string for the specified path (fetchJSONString | rx.fetchJSONString )
mapResult Response -> MoyaMapperResult (Bool, String) ( mapResult | rx.mapResult )
mapObject Response -> Model ( mapObject | rx.mapObject)
mapObjResult Response -> (MoyaMapperResult, Model) ( mapObjResult | rx.mapObjResult)
mapArray Response -> [Model]( mapArray | rx.mapArray)
mapArrayResult Response -> (MoyaMapperResult, [Model]) ( mapArrayResult | rx.mapArrayResult)

❗ Except for fetchJSONString, where the default resolution path is the root path, the default resolution path for all methods is modelKey in the plug-in object

If the json data structure after the interface request is similar to the following figure, MoyaMapper is most appropriate

// Normal
let model = response.mapObject(MMModel.self)
print("name -- \(model.name)")
print("github -- \(model.github)")

/ / print json
print(response.fetchJSONString())

// Rx
rxRequest.mapObject(MMModel.self)
    .subscribe(onSuccess: { (model) in
        print("name -- \(model.name)")
        print("github -- \(model.github)")
    }).disposed(by: disposeBag)
Copy the code

Attached: fetchJSONString detailed use

// Normal
let models = response.mapArray(MMModel.self)
let name = models[0].name
print("count -- \(models.count)")
print("name -- \(name)")

// Prints the name of the first in the JSON model array
print(response.fetchString(keys: [0."name"]))

// Rx
rxRequest.mapArray(MMModel.self)
    .subscribe(onSuccess: { models in
        let name = models[0].name
        print("count -- \(models.count)")
        print("name -- \(name)")
    }).disposed(by: disposeBag)
Copy the code

Attached: Detailed instructions for mapArray

// Normal
let (isSuccess, tipStr) = response.mapResult()
print("isSuccess -- \(isSuccess)")
print("tipStr -- \(tipStr)")

// Rx
rxRequest.mapResult()
    .subscribe(onSuccess: { (isSuccess, tipStr) in
        print("isSuccess -- \(isSuccess)") // Whether it is "000"
        print("retMsg -- \(retMsg)") // "Necessary parameters are missing"
    }).disposed(by: disposeBag)
Copy the code

Attached: detailed instructions for using mapResult

Unified processing of network request results

In the actual use of APP, we will encounter a variety of network request results, such as: the server is down, the mobile phone has no network, and the Response returned by Moya is nil, so we have to judge Error. But using the MoyaMapperPlugin lets us focus only on Response

// The MoyaMapperPlugin initialization method
public init<T: ModelableParameterType> (_ type: T,
    transformError: Bool = true
)

type : ModelableParameterTypeDefines field paths that are used to parse data globally. TransformError:BoolWhether to automatically convert the request result when a network request fails. The default value istrue
Copy the code
  • When the request failsresult.responsenil, according to thetransformErrorWhether it istrueDetermines whether to create a customresponseAnd back out.

Data content that can be requested

Offline now, request data again

  • Normally, when you’re not doing anything and you’re not doing anything, Response is nil

  • After processing by MoyaMapperPlugin, the transformed Response can be obtained, as shown in the figure

RetStatus is mmStatuscode. loadFail regardless of whether the server or its own network is faulty, but errorDescription remains the same and is assigned to retMsg.

  • retStatusThe value will be from the enumerationMMStatusCodeTo takeloadFail.rawValue, i.e.,700
  • Take type forModelableParameterTypetypestatusCodeKeyThe value specified is the key name,retMsgAlso in the same way

Ps: this time can pass judgment retStatus or response. The statusCode whether with MMStatusCode loadFail. RawValue same to judge whether the failure of blank page display placeholder figure

enum MMStatusCode: Int {
    case cache = 230
    case loadFail = 700
}
Copy the code

LoadFail enumeration MMStatusCode, in addition to, and the cache, we already know loadFail data loading in failure occur, when the cache is out? Don’t worry. You’ll find out in the next section.

Data cache

First, basic use
// cache @discardableresult mmcache.shared. cache 'XXX' (value: XXX, key: String, cacheContainer: MMCache.CacheContainer =.ram) -> Bool MMCache. Shared. fetch 'XXX' Cache(key: String, CacheContainer: String) MMCache.CacheContainer = .RAM)Copy the code

A successful cache returns a Bool, which is not accepted here

Types supported by XXX
Bool
Float
Double
String
JSON
Modelable [Modelable]
Moya.Response
Int UInt
Int8 UInt8
Int16 UInt16
Int32 UInt32
Int64 UInt64

Except for moya. Response, all other types are cached through JSON

So, if you want to clear these types of caches, just call the following method

@discardableResult
func removeJSONCache(_ key: String, cacheContainer: MMCache.CacheContainer = .RAM) -> Bool

@discardableResult
func removeAllJSONCache(cacheContainer: MMCache.CacheContainer = .RAM) -> Bool
Copy the code

To clear moya. Response, use the following two methods

@discardableResult
func removeResponseCache(_ key: String) -> Bool

@discardableResult
func removeAllResponseCache(a) -> Bool
Copy the code

Look again at MMCache.CacheContainer

enum CacheContainer {
    case RAM 	// The container is only cached in memory
    case hybrid // A container cached in memory and disk
}
Copy the code

The two containers are not connected, that is, even if the key is the same, the value cannot be obtained through RAM after using hybrid cache.

  • RAM: It is only cached in memory, and the cached data will last for the duration of APP use
  • Hybrid: Cached in memory and disk. Data can be obtained even after the APP restarts
Second, cache network requests

Internal cache process:

  1. When the APP starts for the first time and makes a network request, network data will be cached
  2. When the APP starts again and makes a network request, the cached data will be returned first, and the network data will be returned after the request is successful
  3. In other cases only network data is loaded
  4. The cached data is updated with each successful request
// Normal
func cacheRequest(
    _ target: Target, 
    cacheType: MMCache.CacheKeyType = .default, 
    callbackQueue: DispatchQueue? = nil, 
    progress: Moya.ProgressBlock? = nil, 
    completion: @escaping Moya.Completion
) -> Cancellable

// Rx
func cacheRequest(
    _ target: Base.Target, 
    callbackQueue: DispatchQueue? = nil, 
    cacheType: MMCache.CacheKeyType = .default
) -> Observable<Response>
Copy the code

The Response to the Moya request is actually cached. CacheType: MMCache.CacheKeyType: MMCache.CacheKeyType: MMCache

Here is the definition of MMCache.CacheKeyType

/ * *let cacheKey = [method]baseURL/path
 
 - default : cacheKey + "?" + parameters
 - base : cacheKey
 - custom : cacheKey + "?" + customKey
 */
public enum CacheKeyType {
    case `default`
    case base
    case custom(String)
}
Copy the code

If you want to cache the latest page of multi-page list data, default is not appropriate, because the key used by default contains pageIndex, which would not only cache the latest page of data. So you should use base or custom(String)

We can try caching requests

/* * The first time the APP starts and makes a network request, the network data will be cached * * The next time the APP starts and makes a network request, the cache will be loaded first, then the network data will be loaded * in other cases, only the network data will be loaded */ Each time the data is successfully requested, the data will be updated */
lxfNetTool.rx.cacheRequest(.data(type: .all, size: 10, index: 1))
    .subscribe(onNext: { response in
        log.debug("statusCode -- \(response.statusCode)")
    }).disposed(by: disposeBag)

// The traditional way
/* let _ = lxfNetTool.cacheRequest(.data(type: .all, size: 10, index: 1)) { result in guard let resp = result.value else { return } log.debug("statusCode -- \(resp.statusCode)") } */
Copy the code

Print the result

// Use APP statusCode -- 200 for the first time // close and reopen APP, request statusCode -- 230 statusCode -- 200 // Request statusCode -- 200 againCopy the code

Here is 230 MMStatusCode. Cache. RawValue

CocoaPods

  • The default installation

MoyaMapper only installs files under Core by default

pod 'MoyaMapper'
Copy the code
  • RxSwift expand
pod 'MoyaMapper/Rx'
Copy the code
  • Cache expand
pod 'MoyaMapper/MMCache'
Copy the code
  • The Rx cache
pod 'MoyaMapper/RxCache'
Copy the code