An overview,

1. I believe that Moya is the preferred network tool when using Swift development. In terms of model analysis, there are many third-party libraries related to model analysis in Swift version, SwiftyJSON is the one I am most used to using.

2. The main development features and ideas will be explained below.

3. The following is based on the premise that you can use Moya and SwiftJSON. If you can’t use Moya and SwiftJSON, you can read this article later

Second, function development and thought explanation

1. Try model analysis

Moya requests that the data returned by the server be returned to us in Response class, so we will make an extension to the Response class. Here we take the analytic model as an example

// We need to pass in a parameter that tells us what model to convert
public func mapObject<T: Modelable>(_ type: T.Type) -> T {
    // Model parsing process.return T
}
Copy the code

Q: What about the middle analysis process?

A: Developers can be made to adhere to A protocol, implement specified transformation methods and describe transformation relationships. We don’t need to know the transformation process, just leave it to the developers.

Then we define a protocol, Modelable, and declare transformation methods

public protocol Modelable {
    mutating func mapping(_ json: JSON)
}
Copy the code

The developer creates a MyMoel structure, complies with the Modelable protocol, and implements mapping to write transformation relationships

struct MyModel: Modelable {
    var _id = ""
    
    mutating func mapping(_ json: JSON) {
        self._id = json["_id"].stringValue
    }
}
Copy the code

To put it in perspective, mapObject allows developers to pass in model types, whereas our protocol method is not a class method. Then we need to get the object of the model type and call the mapping method

2. Driven development of model analysis

Q: How do I get this object?

A: You can declare an initialization method in the protocol to create objects. Yes, we create the object corresponding to the model type in mapObject, call the mapping method to transform the data, and then send the model object out.

So we declare an init method in Modelable and pass in a parameter, different from the other initialization methods

public protocol Modelable {
    mutating func mapping(_ json: JSON)
    init(_ json: JSON)}Copy the code

OK, now add the mapObject method to the model parsing process

public func mapObject<T: Modelable>(_ type: T.Type) -> T {
    let modelJson = JSON(data)["modelKey"]
    // Model parsing process
    var obj = T.init(modelJson)
    obj.mapping(modelJson)
    return obj
}
Copy the code

3. Customize the resolution key name

Q: That’s all right, but the json format that comes back from the network is very complicated. Is there a way for the developer to specify the key name for the model?

A: HMM, since the parsing process is performed in the Response extension, we can define the key name attribute through the protocol, and use the Runtime to dynamically add A attribute to the Response to record the corresponding class name after the protocol is complied

public protocol ModelableParameterType {
    /// The value of the status code when the request succeeds
    static var successValue: String { get }
    // the key corresponding to the status code
    static var statusCodeKey: String { get }
    /// The key corresponding to the prompt after the request
    static var tipStrKey: String { get }
    /// The key of the main model data after the request
    static var modelKey: String { get}}Copy the code
// MARK:- runtime
extension Response {
    private struct AssociatedKeys {
        static var lxf_modelableParameterKey = "lxf_modelableParameterKey"
    }
    var lxf_modelableParameter: ModelableParameterType.Type {
        get {
            let value = objc_getAssociatedObject(self, &AssociatedKeys.lxf_modelableParameterKey) as AnyObject
            guard let type = value as? ModelableParameterType.Type else { return NullParameter.self }
            return type
        } set {
            objc_setAssociatedObject(self, &AssociatedKeys.lxf_modelableParameterKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)}}}Copy the code

If we’re not storing OC objects, then objc_getAssociatedObject retrieves values of type _SwiftValue, as? ModelableParameterType. The Type is nil, need after out as AnyObject again converted into other types will succeed ~ ~

Developers can now create a class that complies with the ModelableParameterType protocol and customize the resolution key name

struct NetParameter : ModelableParameterType {
    static var successValue: String { return "false" }
    static var statusCodeKey: String { return "error" }
    static var tipStrKey: String { return "errMsg" }
    static var modelKey: String { return "results"}}Copy the code

4. Plug-in injection

Q: Cool, but when should I store this NetParameter with a custom key name?

A: Well, it’s ~ ~ ~ oh, by the way, through Moya’s plugin mechanism!

Look for plugin. Swift in Moya, find the process method, and read the method description.

/// can be used to modify the result of the request before ending
/// Called to modify a result before completion.
func process(_ result: Result<Moya.Response, MoyaError>, target: TargetType) -> Result<Moya.Response.MoyaError>
Copy the code

We will also create a plugin called MoyaMapperPlugin for developers to use. When creating the plugin, we will pass in the type of the custom parse key name

public struct MoyaMapperPlugin: PluginType {
    var parameter: ModelableParameterType.Type
    
    public init<T: ModelableParameterType> (_ type: T.Type) {
        parameter = type
    }
    
    // modify response
    public func process(_ result: Result<Response, MoyaError>, target: TargetType) -> Result<Response.MoyaError> {
        _ = result.map { (response) -> Response in
            // Take the opportunity to add relevant data
            response.lxf_modelableParameter = parameter
            return response
        }
        return result
    }
}
Copy the code

Use: Developers inject plug-ins when creating MoyaProvider objects. (OS: This step is called “soul injection”)

MoyaProvider<LXFNetworkTool>(plugins: [MoyaMapperPlugin(NetParameter.self)])
Copy the code

5, summary

So that’s the main process. Model array parsing and specified parsing are similar, so I won’t go into them here. I have packaged it as an open source library, MoyaMapper, which contains the above and unexplained features. Here’s how to use it. The above part can be called the appetizer, the purpose is to smooth the transition to the specific use of MoyaMapper below.

Using MoyaMapper’s default library, Core, may not be very effective. However, if you are also using RxSwift to develop projects, please install ‘MoyaMapper/Rx’, absolutely: ‘cool’.

The use of MoyaMapper

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

Define and inject custom keyname classes

  1. Define a structure that complies with the ModelableParameterType protocol
// Refer to the previous JSON data comparison diagram for the returned contents of each parameter
struct NetParameter : ModelableParameterType {
    static var successValue: String { return "false" }
    static var statusCodeKey: String { return "error" }
    static var tipStrKey: String { return "" }
    static var modelKey: String { return "results"}}Copy the code

In addition, simple path handling can be done here to accommodate various situations, separated by ‘>’

Error: {'errorStatus':false 'errMsg':'error Argument type'}Copy the code
// We specify the parsing path: the errMsg field of the error object, which can be expressed layer by layer
static var tipStrKey: String { return "error>errMsg" }
Copy the code
  1. Pass it as a plugin to the MoyaProvider
// MoyaMapperPlugin just needs to pass in the type
MoyaProvider<LXFNetworkTool>(plugins: [MoyaMapperPlugin(NetParameter.self)])
Copy the code

Define the analytical model

Create a structure that complies with the Modelable protocol

struct MyModel: Modelable {
    
    var _id = "".init(_ json: JSON) {}mutating func mapping(_ json: JSON) {
        self._id = json["_id"].stringValue ... }} to comply withModelableProtocol, the two methods that implement the protocol, describe the detailed parsing of model fields in the 'mapping' methodCopy the code

Parse the data

0x00 Request results and model parsing

// Result
public func mapResult(params: ModelableParamsBlock? = nil) -> MoyaMapperResult

// Model
public func mapObject<T: Modelable>(_ type: T.Type, modelKey: String? = nil) -> T
// Result+Model
public func mapObjResult<T: Modelable>(_ type: T.Type, params: ModelableParamsBlock? = nil)- > (MoyaMapperResult.T)

// Models
public func mapArray<T: Modelable>(_ type: T.Type, modelKey: String? = nil)- > [T]
// Result+Models
public func mapArrayResult<T: Modelable>(_ type: T.Type, params: ModelableParamsBlock? = nil)- > (MoyaMapperResult[T])
Copy the code

Above five methods, from its name, know its meaning, here is no more explanation, mainly pay attention to two points:

  • result
// Primitive type
// Parameter 1: Is the value fetched based on statusCodeKey equal to successValue
// Parameter 2: the value extracted by tipStrKeyThe result:Bool.String)
Copy the code
  • params
// params: ModelableParamsBlock? = nil
// This is used only in special cases. For example, if the project needs to use a specific interface somewhere, but the json format returned is not the same as your project's, and there are only one or two places where the extra interface is needed, we need this parameter, which returns the parsed parameter type as a Block.
Copy the code

0x01, specific resolution

// Model
public func toJSON(modelKey: String? = nil) -> JSON
// Gets the value of the specified path
public func fetchJSONString(path: String? = nil, keys: [JSONSubscriptType]) -> String
Copy the code

Both methods, if no path is specified, default to modelKey

// fetchJSONString(keys: <[JSONSubscriptType]>)
1, pass an array through keys. The array can be passed in of typeIntString
2, the default path is shown in modelKey to obtain the corresponding value. If modelKey is not the parse path you want to use, you can simply respecify the path using the overloaded method below// response.fetchJSONString(path: 
      
       , keys: <[JSONSubscriptType]>)
      ?>
Copy the code

MoyaMapper also provides Rx sub-libraries for easy data parsing under RxSwift streaming programming

MoyaMapper only installs pod files under Core by default'MoyaMapper'RxSwift expand pod'MoyaMapper/Rx'
Copy the code

For those of you who are not quite sure how to use it, download and run Example

ifMoyaMapperAny deficiencies, welcome to issue, thank you for your support