iOS日常首页投稿(暂停使用,暂停投稿)swift学习笔记

Swift开源项目ObjectMapper实践

2017-07-20  本文已影响509人  Leafly

近期项目打算全面向swift迁移,虽然两三年前有写过swift项目但是很长时间没有开发很多知识点已经模糊,最近打算就热门的几个第三方库的使用方法进行一个调研

今天就先从ObjectMapper入手,ObjectMapper是一个由swift写的json和模型转换的开源库,目前已经有5950个star

先从官方文档入手,进行一个简单的介绍

支持的功能
基础用法

ObjectMapper中定义了一个协议Mappable

Mappable协议中声明了两个方法

mutation func mapping(map: Map)

init?(map: Map)

我们需要在模型中遵循这个协议,官方给出了一个参考:

class User: Mappable {
    var username: String?
    var age: Int?
    var weight: Double!
    var array: [Any]?
    var dictionary: [String : Any] = [:]
    var bestFriend: User?                       // Nested User object
    var friends: [User]?                        // Array of Users
    var birthday: Date?

    required init?(map: Map) {

    }

    // Mappable
    func mapping(map: Map) {
        username    <- map["username"]
        age         <- map["age"]
        weight      <- map["weight"]
        array       <- map["arr"]
        dictionary  <- map["dict"]
        bestFriend  <- map["best_friend"]
        friends     <- map["friends"]
        birthday    <- (map["birthday"], DateTransform())
    }
}

struct Temperature: Mappable {
    var celsius: Double?
    var fahrenheit: Double?

    init?(map: Map) {

    }

    mutating func mapping(map: Map) {
        celsius     <- map["celsius"]
        fahrenheit  <- map["fahrenheit"]
    }
}

一旦我们的类或结构体如上面的示例一样实现了协议,我们就可以方便的进行JSON和模型之间的转换

let user = User(JSONString: JSONString)

let JSONString = user.toJSONString(prettyPrint: true)

当然也可以通过Mapper类来进行转换

let user = Mapper<User>().map(JSONString: JSONString)

let JSONString = Mapper().toJSONString(user, prettyPrint: true)
嵌套对象的映射

正如前面所列,ObjectMapper支持嵌套对象的映射

列如:

{
    "distance" : {
        "text" : "102",
        "value" : 31
    }
}

我们想要直接取出distance对象中的value值,可以设置如下mapping

func mapping(map: Map) {
    distance <- map["distance.value"]
}
自定义转换规则

ObjectMapper允许开发者在数据映射过程中指定转换规则

class People: Mappable {
   var birthday: NSDate?
   
   required init?(_ map: Map) {
       
   }
   
   func mapping(map: Map) {
       birthday <- (map["birthday"], DateTransform())
   }
   
   let JSON = "\"birthday\":1458117795332"
   let result = Mapper<People>().map(JSON)
}

由于我们指定了birthday的转换规则,所以上述代码在解析JSON数据的时候会将long类型转换成Date类型

除了使用ObjectMapper给我们提供的转换规则外,我们还可以通过实现TransformType协议来自定义我们的转换规则

public protocol TransformType {
    typealias Object
    typealias JSON
    
    func transformFromJSON(value: AnyObject?) -> Object?
    func transformToJSON(value: Object?) -> JSON?
}

ObjectMapper为我们提供了一个TransformOf类来实现转换结果,TransformOf实际就是实现了TransformType协议的,TransformOf有两个类型的参数和两个闭包参数,类型表示参与转换的数据的类型,闭包表示转换的规则

let transform = TransformOf<Int, String>(fromJSON: { (value: String?) -> Int? in 
}, toJSON: { (value: Int?) -> String? in 
  // transform value from Int? to String?
  if let value = value {
      return String(value)
  }
  return nil
})

id <- (map["id"], transform)
泛型对象

ObjectMapper同样可以处理泛型类型的参数,不过这个泛型类型需要在实现了Mappable协议的基础上才可以正常使用

class User: Mappable {
    var name: String?
    
    required init?(_ map: Map) {
        
    }
    
    func mapping(_ map: Map) {
        name <- map["name"]
    }
}

class Result<T: Mappable>: Mappable {
    var result: T?
    
    required init?(_ map: Map) {
        
    }
    
    func mapping(map: Map) {
        result <- map["result"]
    }
}

let JSON = "{\"result\": {\"name\": \"anenn\"}}"
let result = Mapper<Result<User>>().map(JSON)

原理解析

ObjectMapper声明了一个Mappable协议,这个协议里声明了两个方法

init?(map: Map)

mutating func mapping(map: Map)

并且通过对Mappable协议的扩展,增加了JSON和模型之间的四个转换方法

public extension BaseMappable {
    
    /// Initializes object from a JSON String
    public init?(JSONString: String, context: MapContext? = nil) {
        if let obj: Self = Mapper(context: context).map(JSONString: JSONString) {
            self = obj
        } else {
            return nil
        }
    }
    
    /// Initializes object from a JSON Dictionary
    public init?(JSON: [String: Any], context: MapContext? = nil) {
        if let obj: Self = Mapper(context: context).map(JSON: JSON) {
            self = obj
        } else {
            return nil
        }
    }
    
    /// Returns the JSON Dictionary for the object
    public func toJSON() -> [String: Any] {
        return Mapper().toJSON(self)
    }
    
    /// Returns the JSON String for the object
    public func toJSONString(prettyPrint: Bool = false) -> String? {
        return Mapper().toJSONString(self, prettyPrint: prettyPrint)
    }
}

我们在使用ObjectMapper进行转换时,首先需要让模型遵循Mappable协议,并且实现Mappable声明的两个方法

class User: Mappable {
    var username: String?
    var age: Int?

    required init?(map: Map) {

    }

    // Mappable
    func mapping(map: Map) {
        username    <- map["username"]
        age         <- map["age"]
    }
}

因为ObjectMapper为Mappable增加了四个转换方法,所以User也继承了这四个转换方法

let user = User(JSONString: JSONString)

这个方法的调用其实就是帮助我们执行了

Mapper().map(JSONString: JSONString)

因此,我们也可以直接通过如下方法进行转换

let user = Mapper<User>().map(JSONString: JSONString)

接下来就是ObjectMapper的核心啦~
为什么通过Mapper类能完成转换呢?

我们来逐步了解Mapper的实现原理

public final class Mapper<N: BaseMappable> {
    
}

正如上面的代码块所示,首先Mapper类定义了一个泛型N,N必须要遵循Mappable协议,也就是我们的模型User
继续往下看,我们选择一个常用的方法

public func map(JSONString: String) -> N? {
        if let JSON = Mapper.parseJSONStringIntoDictionary(JSONString: JSONString) {
            return map(JSON: JSON)
        }
        
        return nil
    }

没错,我们前面写的Mapper的转换方法正是使用的这个方法

Mapper().map(JSONString: JSONString)

那我们就来一层一层详细分析一下这个方法的内部实现吧

// 1. 调用Mapper的静态方法parseJSONStringIntoDictionary来将JSON转换成字典
// 2. 调用map方法将转换后的字典转换成模型并返回
if let JSON = Mapper.parseJSONStringIntoDictionary(JSONString: JSONString) {
            return map(JSON: JSON)
        }
        
        return nil

先分解parseJSONStringIntoDictionary的实现

/// Convert a JSON String into a Dictionary<String, Any> using NSJSONSerialization
    public static func parseJSONStringIntoDictionary(JSONString: String) -> [String: Any]? {
        let parsedJSON: Any? = Mapper.parseJSONString(JSONString: JSONString)
        return parsedJSON as? [String: Any]
    }

    /// Convert a JSON String into an Object using NSJSONSerialization
    public static func parseJSONString(JSONString: String) -> Any? {
        let data = JSONString.data(using: String.Encoding.utf8, allowLossyConversion: true)
        if let data = data {
            let parsedJSON: Any?
            do {
                parsedJSON = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments)
            } catch let error {
                print(error)
                parsedJSON = nil
            }
            return parsedJSON
        }

        return nil
    }

通过上述代码的逻辑不难发现,parseJSONStringIntoDictionary就是利用了系统的JSONSerialization将JSON字符串转换成字典

理解了第一步,接下来我们就来看看第二步的实现原理

我们先整体看一下map方法的实现代码

/// Maps a JSON dictionary to an object that conforms to Mappable
    public func map(JSON: [String: Any]) -> N? {
        let map = Map(mappingType: .fromJSON, JSON: JSON, context: context, shouldIncludeNilValues: shouldIncludeNilValues)
        
        if let klass = N.self as? StaticMappable.Type { // Check if object is StaticMappable
            if var object = klass.objectForMapping(map: map) as? N {
                object.mapping(map: map)
                return object
            }
        } else if let klass = N.self as? Mappable.Type { // Check if object is Mappable
            if var object = klass.init(map: map) as? N {
                object.mapping(map: map)
                return object
            }
        } else if let klass = N.self as? ImmutableMappable.Type { // Check if object is ImmutableMappable
            do {
                return try klass.init(map: map) as? N
            } catch let error {
                #if DEBUG
                let exception: NSException
                if let mapError = error as? MapError {
                    exception = NSException(name: .init(rawValue: "MapError"), reason: mapError.description, userInfo: nil)
                } else {
                    exception = NSException(name: .init(rawValue: "ImmutableMappableError"), reason: error.localizedDescription, userInfo: nil)
                }
                exception.raise()
                #else
                NSLog("\(error)")
                #endif
            }
        } else {
            // Ensure BaseMappable is not implemented directly
            assert(false, "BaseMappable should not be implemented directly. Please implement Mappable, StaticMappable or ImmutableMappable")
        }
        
        return nil
    }

我们先忽略掉StaticMappableImmutableMappable这两种协议的处理逻辑,直接关注最重要的Mappable协议的实现

// 根据传入的JSON字典等数据创建一个map对象

let map = Map(mappingType: .fromJSON, JSON: JSON, context: context, shouldIncludeNilValues: shouldIncludeNilValues)

// 判断要转换成的模型是不是 遵循的Mappable协议
if let klass = N.self as? Mappable.Type 

// 创建一个N类型的对象
var object = klass.init(map: map) as? N

// 获取模型中定义的解析规则,完成解析
object.mapping(map: map)

// 返回生成好的模型
return object

这里还留了一个疑问,为何执行完object.mapping(map: map)后,模型就能完成解析呢

继续分析[Map]((https://github.com/Hearst-DD/ObjectMapper/blob/master/Sources/Mapper.swift)类

原来在Map类中使用了subscript来自定义下标
同样我们直接来分析最重要的那个自定义下标的方法

public subscript(key: String, nested nested: Bool, delimiter delimiter: String, ignoreNil ignoreNil: Bool) -> Map {
        // save key and value associated to it
        currentKey = key
        keyIsNested = nested
        nestedKeyDelimiter = delimiter
        
        if mappingType == .fromJSON {
            // check if a value exists for the current key
            // do this pre-check for performance reasons
            if nested == false {
                let object = JSON[key]
                let isNSNull = object is NSNull
                isKeyPresent = isNSNull ? true : object != nil
                currentValue = isNSNull ? nil : object
            } else {
                // break down the components of the key that are separated by .
                (isKeyPresent, currentValue) = valueFor(ArraySlice(key.components(separatedBy: delimiter)), dictionary: JSON)
            }
            
            // update isKeyPresent if ignoreNil is true
            if ignoreNil && currentValue == nil {
                isKeyPresent = false
            }
        }
        
        return self
    }

不难发现,在这方法中,我们从JSON字典中根据key获取了value,原来当我们调用object.mapping(map: map)时,就会依次根据我们配置的mapping值获取对应的value

ObjectMapper实践

分析完原理,我们还需要能够熟练的运用ObjectMapper来帮助我们完成解析功能,ObjectMapper能够帮助我们处理数据的方法有很多,这里我就先简单跟大家分享几种在项目中常用的方法,我已经将相关的Demo上传到github上,欢迎大家star

解析单一结构的模型

单一结构的模型解析比较简单,大家了解一下即可

{
  "name": "objectmapper",
  "age": 18,
  "nickname": "mapper",
  "job": "swifter"
}

当我们拿到如上所示的json数据时,只需要创建一个遵循Mappable的模型,并配置好解析路径即可

class User: Mappable {

    var name: String?
    var age: Int?
    var nickname: String?
    var job: String?
    
    required init?(map: Map) {
        
    }
    
    func mapping(map: Map) {
        name <- map["name"]
        age <- map["age"]
        nickname <- map["nickname"]
        job <- map["job"]
    }
    
}

// 使用方法
let user = Mapper<User>().map(JSONString: json.rawString()!)

解析模型中嵌套模型的情况

有时候我们拿到的json数据嵌套了好几层的结构,而我们刚好也需要逐层解析拿到每个模型的数据,下面我就通过一个两层结构给大家演示一下处理的方法

{
  "weather": "sun",
    "temperature": {
        "celsius": 70,
        "fahrenheit": 34
    },
}

正如上面的json数据所示,我们需要创建一个weather模型,同时包含一个temperature模型来解析json数据,这时我们就需要运用泛型来帮助我们达到嵌套的目的

class Weather<T: Mappable>: Mappable {

    var weather: String?
    var temperature: T?
    
    required init?(map: Map) {
        
    }
    
    func mapping(map: Map) {
        weather <- map["weather"]
        temperature <- map["temperature"]
    }
}

class Temperature: Mappable {
    
    var celsius: Double?
    var fahrenheit: Double?
    
    required init?(map: Map) {
        
    }
    
    func mapping(map: Map) {
        celsius <- map["celsius"]
        fahrenheit <- map["fahrenheit"]
    }
}

// 使用方法如下
let weather = Mapper<Weather<Temperature>>().map(JSONString: json.rawString()!)

解析模型中嵌套模型但是我们只需要拿到子模型的属性值

有些json数据嵌套的内容我们不需要分模型来获取,只需要将属性统一到一个模型中使用

{
    "distance": {
        "text": "102 ft",
        "value": 31
    }
}
class Distance: Mappable {
    var text: String?
    var value: Int?
    
    required init?(map: Map) {
        
    }
    
    func mapping(map: Map) {
        text <- map["distance.text"]
        value <- map["distance.value"]
    }

}
解析指定的一个数组
{
    "status": "200",
    "msg": "success",
    "features": [
       {
         "name": "json解析"
       },
       {
         "name": "模型转换"
       },
       {
         "name": "数据处理"
       },
       {
         "name": "mapper进阶"
       }
    ]
}

通过我们拿到的返回结果如上面的代码段所示,features是我们真正关心的数据,它是一个数组结构,我们期望能够得到的是一个数组,里面包含若干个feature模型

首先我们必不可少的就是要创建一个Feature模型

class Feature: Mappable {

    var name: String?
    
    required init?(map: Map) {
        
    }
    
    func mapping(map: Map) {
        name <- map["name"]
    }
    
}

在解析这个JSON数据时,我们可以忽略掉其他信息,先获取features对应的json数据

let featureJson = json["features"];

然后只需要使用Mapper的高级用法mapArray方法即可直接得到数组对象

let features = Mapper<Feature>().mapArray(JSONString: featureJson.rawString()!)

ObjectMapper的高级用法还有很多,掌握上面的这些用法基本已经可以在项目中使用ObjectMapper达到我们的需求了,后面有时间我会在此博客和Demo基础上更新更多的用法供大家参考

上一篇下一篇

猜你喜欢

热点阅读