本帖最后由 Caesar 于 2016-10-14 11:07 编辑

为什么将数据模块与网络模块一起编写?紧密相关的2者,也是应用程序最常见的组合:请求数据-存储数据-展示数据。




1、数据模块(JSON、Model、DB)

数据持久层其实有很多杂乱的选择,文件做为持久层的基础类型,又被分为sql、xml等等类型,我们可选的范围也很广阔,也可以自己动手来完善整个持久层模块,但这似乎对于很懒的程序员来说太浪费玩耍的时间了。所以我们索性在一些完善的开源库中挑选一类供我们做上层的封装。我们姑且确定选择FMDB作为我们的数据库支撑,但是联想到一般情况下,把JSON转换为Model直接存向数据库是常用需求,所以在数据模块这一层貌似扩展的很大了。我们首先引入SwiftyJSON作为JSON解析,不仅供框架使用也供外层使用,但是SwiftJSON解析后的数据仅是JSON对象,看来我们要亲自把JSON对象转换为Model了,写好之后,就是数据的存储。

通常情况下,iOS App中常用的存储:UserDefault、WriteToFile、SQL、钥匙串等。为了框架的通用性,只选择完善SQL,即Model->DB,至于别的存储让App在框架外层随意。如果你的团队不是足够成熟的话, 并不建议在项目中使用CoreData。

想想这个模块比较大,所以单开一个文件夹:Model吧。

首先引入SwiftJSON作为JSON解析的模块。

[Bash shell] 纯文本查看 复制代码
pod 'SwiftyJSON'


明确目标1:

  • JSON对象转换为模型
  • 模型转换为JSON对象


要实现我们这2个功能,必须遵守协议,在协议的方法中返回Mapping供我们赋值,类似:[属性名:JSON对象的Key]。

[Swift] 纯文本查看 复制代码
public protocol ModelMap {
  /// 提供属性的对应Map
  ///
  /// - returns: 字典,为:属性名称:(JSON对象中名称,类型)
  static func keyMapping() ->Dictionary<String, (String, String)>
}


首先我们要了解一些关于Swift runtime的东西,在纯Swift类中的属性在未添加dynamic修饰之前是不可以被运行时获取到属性的,之前我写过一篇有关Swift运行时的文章,可以细阅。

在这里有一个缺陷,就是我们这个库不允许你的模型是继承来的,因为static关键字代替了class final,在子类中不可以被覆盖,使用Extension又不能很好的解决方便程度上的问题而且需要写基类。所以在考虑了很多很多之后,还是决定写扩展!并且不对纯Swift类进行处理,只处理NSObject类。

[Swift] 纯文本查看 复制代码
extension NSObject {
  /// 提供属性的对应Map
  ///
  /// - returns: 字典,为:属性名称:JSON对象中名称
  open class func keyMapping() ->Dictionary<String, String>? {
    return nil
  }
  
  /// 提供的类对应Map
  open class func classMapping() ->Dictionary<String, AnyClass>? {
    return nil
  }
  
  /// 忽略的属性的名称
  ///
  /// - returns: 数组,忽略的属性名称数组
  open class func ignoredKey() ->[String]? {
    return nil
  }
}


这样所有需要被转换的类都需要继承Model类。但是使用的时候就比较尴尬了,必须要提供一个初始值,下面是我们的测试类:

[Swift] 纯文本查看 复制代码
import UIKit
import INSSwift
class Person: NSObject {
  var name: String = ""
  var gender : Bool = false
  var array: Array = [""]
  var dictionary: Dictionary<String, Any> = ["": ""]
  
  var student: Student = Student()
  var teachers: [Teacher] = []
  
  var ignored: String = ""
  
  override class func keyMapping() ->Dictionary<String, String>? {
    return [
      "gender": "dictionary, gender",
      "teachers": "student, t"
    ]
  }
  
  override class func classMapping() ->Dictionary<String, AnyClass>? {
    return [
      "student": Student.self,
      "teachers": Teacher.self
    ]
  }
  
  override class func ignoredKey() ->[String]? {
    return ["ignored"]
  }
}
class Student: NSObject {
  var sname: String = ""
  var sage: Int = 0
}
class Teacher: NSObject {
  var tname: String = ""
}


如果dynamic var name: String?的话,后果自负…会提示错误不能转换为OC类型。这里我附上完整的Model处理的代码以及测试代码,在Model文件夹下新建文件Model.swift:

[Swift] 纯文本查看 复制代码
import Foundation
import SwiftyJSON
public enum PropertyType: String {
  case tString = "T@\"NSString\""
  case tNSArray = "T@\"NSArray\""
  case tNSDictionary = "T@\"NSDictionary\""
  case tBool = "TB"
  case tClass = "T@\"_TtC"
  case tDouble = "Td"
  case tFloat = "Tf"
  case tInt = "Tq"
  case tInt8 = "Tc"
  case tInt16 = "Ts"
  case tInt32 = "Ti"
  case tUInt = "TQ"
  case tUInt8 = "TC"
  case tUInt16 = "TS"
  case tUInt32 = "TI"
  case unknow = "UNKNOWTYPE"
}
extension NSObject {
  /// 提供属性的对应Map
  ///
  /// - returns: 字典,为:属性名称:JSON对象中名称
  open class func keyMapping() ->Dictionary<String, String>? {
    return nil
  }
  
  /// 提供的类对应Map
  open class func classMapping() ->Dictionary<String, AnyClass>? {
    return nil
  }
  
  /// 忽略的属性的名称
  ///
  /// - returns: 数组,忽略的属性名称数组
  open class func ignoredKey() ->[String]? {
    return nil
  }
}
/// 模型生成工厂
final public class ModelFactory {
  
  // MARK: - 对象生成
  
  /// 转换JSON->Model
  ///
  /// - parameter json: JSON对象
  /// - parameter cls: 类型
  /// - returns: AnyObject
  public class func Convert(JSON json: JSON?, to cls: AnyClass) ->AnyObject? {
    let model = cls.alloc()
    let keyMapping = cls.keyMapping()
    let classMapping = cls.classMapping()
    
    setValue(with: fullPropertyFor(class: cls), for: model, with: classMapping, with: keyMapping, data: json)
    return model
  }
  
  // MARK: - Private methods
  
  /// 赋值
  /// - parameter props: 属性对应字典
  /// - parameter object: 赋值的对象
  /// - parameter clsMap: 类型映射字典
  /// - parameter mapping: 属性映射字典
  /// - parameter data: JSON对象
  public class func setValue(with props: Dictionary<String, String>?,
                             for object: AnyObject,
                             with clsMap: Dictionary<String, AnyClass>?,
                             with mapping: Dictionary<String, String>?,
                             data data: JSON?) {
    guard let fullInfos = props, fullInfos.count != 0, let json = data else {
      return
    }
    /// 将所有的Info取出来
    for (key, typeString) in fullInfos {
      /// 首先从Mapping中检测是否有对应的Mapper
      let JSONKey = mapping?[key] ?? key
      var JSONValue = json[JSONKey]
      if JSONKey.contains(",") {
        var items: Array<String> = []
        for item in JSONKey.components(separatedBy: ",") {
          items.append(item.replacingOccurrences(of: " ", with: ""))
        }
        JSONValue = json[items]
      }
      
      let propType = PropertyType(rawValue: typeString)
      
      if let classMapping = clsMap, let pointedClass = classMapping[key] {
        // 该属性实现了类型Map
        if propType == .tClass {
          let sweetObject = Convert(JSON: JSONValue, to: pointedClass)
          object.setValue(sweetObject, forKey: key)
          continue
        }
        else if propType == .tNSArray {
          let list: Array<JSON> = JSONValue.arrayValue
          var objectArray: Array<AnyObject> = []
          for item in list {
            guard let arrayObject = Convert(JSON: item, to: pointedClass) else {
              continue
            }
            objectArray.append(arrayObject)
          }
          object.setValue(objectArray, forKey: key)
          continue
        }
      }
      
      var value: Any? = JSONValue
      if propType == .tString {
        value = JSONValue.stringValue
      }
      if propType == .tInt || propType == .tInt8 || propType == .tInt16 || propType == .tInt32 ||
         propType ==