EVReflection_Swift5 版本 1.0.0

EVReflection_Swift5 版本 1.0.0

Slavenko 维护。



EVReflection_Swift5 版本 1.0.0

  • Slavenko

EVReflection

Issues Coverage Documentation Stars Awesome

Version Carthage compatible Language Platform License

Git Twitter LinkedIn Website eMail

一般信息

目前,主分支已测试与 Swift 4.2 和 5.0 测试版兼容。如果您想在旧版本中继续使用 EVReflection,请使用相应的分支。运行单元测试以查看 EVReflection 的运行情况。

请现在更新到 cocoapods 1.7.0,可以使用以下方式

sudo gem install cocoapods --pre

这是为了支持除 5.0 之外的其他 Swift 版本所必须的库。有关更多信息,请参阅cocoapods 博客

EVReflection 用于 EVCloudKitDaoEVWordPressAPI

在大多数情况下,EVReflection 非常易于使用。只需查看它很容易使用部分。但如果您确实想做一些非常规的特殊事情,EVReflection 会提供广泛的功能。

可用的扩展

有扩展可用于将 EVReflection 与 XMLDictionaryRealmCloudKitAlamofireMoya 结合使用,以及与 RxSwiftReactiveSwift 结合使用。

所有这些扩展都可以通过在 Podfile 中添加类似以下内容来安装

pod 'EVReflection/MoyaRxSwift'

对于 Carthage,还没有上述所有扩展的扩展。如有需要,请告知我。对于 Carthage,您可以使用

github "evermeer/EVReflection" 

索引

EVReflection 的主要功能

  • 根据 NSObject 解析对象到字典,以及从字典到对象。 (也请参阅 XML 和 .plist 示例!)
  • 从 JSON 字符串到对象以及从对象到 JSON 字符串的解析。
  • 支持 NSCoding 函数 encodeWithCoder 和 decodeObjectWithCoder
  • 支持使用所有属性的同时使用 Printable、Hashable 和 Equatable
  • 将一个类型映射到另一个类型
  • 支持属性映射、转换器、验证器和键清理

它很容易使用

定义一个对象。您只需将 EVObject 设置为其基类(或扩展 NSObject,使其符合 EVReflectable)

class User: EVObject {
    var id: Int = 0
    var name: String = ""
    var friends: [User]? = []
}

将 JSON 解析为对象

let json:String = "{\"id\": 24, \"name\": \"Bob Jefferson\", \"friends\": [{\"id\": 29, \"name\": \"Jen Jackson\"}]}"
let user = User(json: json)

将 JSON 解析为对象数组

let json:String = "[{\"id\": 27, \"name\": \"Bob Jefferson\"}, {\"id\": 29, \"name\": \"Jen Jackson\"}]"
let array = [User](json: json)

从字典到字典的解析

let dict = user.toDictionary()
let newUser = User(dictionary: dict)
XCTAssert(user == newUser, "Pass")

保存和加载对象到文件和从文件中

user.saveToTemp("temp.dat")
let result = User(fileNameInTemp: "temp.dat")
XCTAssert(theObject == result, "Pass")

将对象映射到其他类型

let administrator: Administrator = user.mapObjectTo()

如果您有 XML 而不是 JSON

如果您想做到相同的事情但使用的是 XML,那么您可以使用 XML 子规范 'pod EVReflection/XML',这是一个简单的解析 XML 的方法。使用它,您的代码将看起来像这样

let xml = "<user><id>27</id><name>Bob</name><friends><user><id>20</id><name>Jen</name></user></friends></user>"
let user = User(xmlString: xml)

在您的应用程序中使用 EVReflection

'EVReflection' 通过依赖关系管理器 CocoaPods 提供。您必须使用 CocoaPods 版本 0.36 或更高版本

您只需通过在您的 Podfile 中添加以下 2 行,即可将 EVReflection 添加到您的工作区

use_frameworks!
pod "EVReflection"

您还可以使用 EVReflection 的 Swift2.2 或 Swift2.3 版本。您可以使用 podfile 命令获取此版本

use_frameworks!
pod "EVReflection"', :git => 'https://github.com/evermeer/EVReflection.git', :branch => 'Swift2.2'

cocoapods 的 0.36 版本会将您使用的所有 pod 转换为动态框架。因此,它仅在 iOS 8.0 或更高版本上受支持。当使用框架时,您还必须在 swift 文件的顶部添加一个导入,如下所示

import EVReflection

如果需要支持低于 iOS 8.0 的旧版本,您还可以将 pod 文件夹中的文件复制到您的项目。您必须使用 Swift2.3 版本或更早版本。iOS 7 支持已在 Swift 3 中取消

请注意,当您的对象定义在框架中而不是在主应用中时,您必须让 EVReflection 知道它也应该在该框架中查找您的类。这可以很容易地通过以下单行代码来完成(例如在 appdelegate 中)

EVReflection.setBundleIdentifier(YourDataObject.self)

更多示例代码

将 EVReflection 克隆到您的桌面以查看这些以及其他单元测试

func testEquatable() {
    var theObjectA = TestObject2()
    theObjectA.objectValue = "value1"
    var theObjectB = TestObject2()
    theObjectB.objectValue = "value1"
    XCTAssert(theObjectA == theObjectB, "Pass")

    theObjectB.objectValue = "value2"
    XCTAssert(theObjectA != theObjectB, "Pass")
}

func testHashable() {
    var theObject = TestObject2()
    theObject.objectValue = "value1"
    var hash1 = theObject.hash
    NSLog("hash = \(hash)")
}

func testPrintable() {
    var theObject = TestObject2()
    theObject.objectValue = "value1"
    NSLog("theObject = \(theObject)")
}

func testArrayFunctions() {
    let dictionaryArray: [NSDictionary] = yourGetDictionaryArrayFunction()
    let userArray = [User](dictionaryArray: dictionaryArray)
    let newDictionaryArray = userArray.toDictionaryArray()
}

func testMapping() {
    let player = GamePlayer()
    player.name = "It's Me"

    let administrator = GameAdministrator(usingValuesFrom: player)
}

从 NSDictionary(或 NSDictionaries 数组)直接转换为 json 以及反向操作。

let dict1: NSDictionary = [
  "requestId": "request",
  "postcode": "1111AA",
  "houseNumber": "1"
]
let json = dict1.toJsonString()
let dict2 = NSMutableDictionary(json: json)
print("dict:\n\(dict1)\n\njson:\n\(json)\n\ndict2:\n\(dict2)")

// You can do the same with arrays
let array:[NSDictionary] = [dict1, dict2]
let jsonArray = array.toJsonStringArray()
let array2 = [NSDictionary](jsonArray: jsonArray)
print("json array: \n\(jsonArray)\n\narray2:\n\(array2)")

以下是解析 .plist 到对象模型的方法。查看 EVReflectionIssue124.swift 以查看其效果。

   if let path = Bundle(for: type(of: self)).path(forResource: "EVReflectionIssue124", ofType: "plist") {
       if let data = NSDictionary(contentsOfFile: path) {
          let plistObject = Wrapper(dictionary: data)
          print(plistObject)
       }
   }

如果您想解析 XML,则可以使用 pod 子规范 EVReflection/XML

    let xml: String = "<data><item name=\"attrib\">itemData</item></data>"
    let xmlObject = MyObject(xml: xml)
    print(xmlObject)

扩展现有对象

可以扩展其他对象以使用 EVReflectable 协议,而不是更改基类以使用 EVObject。这将使您能够将这些对象添加到具有每个框架的需要中。在某些情况下,您可能仍然需要一些额外的代码。有关示例,请参阅 Realm 和 NSManagedObject 子规范。扩展您的对象的最基本方法如下所示

import EVReflection
extension MyObject : EVReflectable { }

额外信息

转换选项

几乎每个 EVReflection 函数都可以指定应使用的转换选项类型。这是使用选项集完成的。您可以使用以下转换选项

  • None - 不使用任何转换函数。
  • 属性转换器:如果指定,将调用 EVObject 上的 propertyConverters 函数
  • 属性映射:如果指定,将调用 EVObject 上的 propertyMapping 函数
  • 跳过属性值:如果指定,将调用 EVObject 上的 skipPropertyValue 函数
  • 键清理:如果指定,将调用自动的 PascalCase 和 snake_case 属性键映射。
  • 编码:如果您想要类级功能来编码值(例如 base64、unicode、加密等)
  • 解码:如果您想要类级功能来解码值(例如 base64、unicode、加密等)

在 EVReflection 中,所有函数都将使用针对其功能的特定默认转换选项。以下使用 4 种默认转换类型

  • 默认 NSCoding = [None]
  • 默认比较 = [PropertyConverter, PropertyMapping, SkipPropertyValue]
  • 默认反序列化 = [PropertyConverter, PropertyMapping, SkipPropertyValue, KeyCleanup, Decoding]
  • 默认序列化 = [PropertyConverter, PropertyMapping, SkipPropertyValue, Encoding]

如果您想更改其中一个默认转换类型,可以通过类似于下面的方式来完成

ConversionOptions.DefaultNSCoding = [.PropertyMapping]

为 Swift 关键字自动执行关键字映射

如果您的 JSON 字段是 Swift 关键字,那么请使用下划线作为属性的名称前缀。例如,存储 self 的 JSON 值将保存在名为 \_self 的属性中。目前以下是已处理的关键字

"self","description","class","deinit","enum","extension","func","import","init","let","protocol","static","struct","subscript","typealias","var","break","case","continue","default","do","else","fallthrough","if","in","for","return","switch","where","while","as","dynamicType","is","new","super","Self","Type","COLUMN","FILE","FUNCTION","LINE","associativity","didSet","get","infix","inout","left","mutating","none","nonmutating","operator","override","postfix","precedence","prefix","right","set","unowned","unowned","safe","unowned","unsafe","weak","willSet","private","public"

自动将 PascalCase 或 camelCase 映射到 snake_case

在从 JSON 创建对象时,EVReflection 会自动检测是否应将 snake_case(键完全小写且单词之间以下划线分隔)转换为 PascalCase 或 camelCase 属性名称。有关此函数何时将被调用的信息,请参阅 转换选项

在将对象导出为字典或 JSON 字符串时,您可以指定您是否希望转换为 snake_case。默认为 .DefaultDeserialize,它也将转换为 snake case。

let jsonString = myObject.toJsonString([.DefaultSerialize])
let dict = myObject.toDictionary([PropertyConverter, PropertyMapping, SkipPropertyValue])

自定义关键字映射

您还可以创建自定义属性映射。您可以为是否忽略了导入、是否忽略了导出或者是将属性映射为另一个键(对于字典和 JSON)进行定义。为此,您只需要在对象中实现 propertyMapping 函数。有关此函数何时将被调用的信息,请参阅 转换选项

public class TestObject5: EVObject {
    var Name: String = "" // Using the default mapping
    var propertyInObject: String = "" // will be written to or read from keyInJson
    var ignoredProperty: String = "" // Will not be written or read to/from json 

    override public func propertyMapping() -> [(keyInObject: String?, keyInResource: String?)] {
        return [(keyInObject: "ignoredProperty",keyInResource: nil), (keyInObject: "propertyInObject",keyInResource: "keyInJson")]
    }
}

自定义属性转换器

您还可以使用自己的属性转换器。为此,您需要在您的对象中实现 propertyConverters 函数。对于每个属性,您可以为属性创建一个自定义的 getter 和 setter,然后由 EVReflection 使用。下面是示例,其中 JSON 文本 'Sure' 和 'Nah' 将被转换为 isGreat 属性中的 true 或 false。有关此函数何时将被调用的信息,请参阅 转换选项

public class TestObject6: EVObject {
    var isGreat: Bool = false

    override func propertyConverters() -> [(key: String, decodeConverter: ((Any?) -> ()), encodeConverter: (() -> Any?))] {
        return [
            ( // We want a custom converter for the field isGreat
              key: "isGreat"
              // isGreat will be true if the json says 'Sure'
              , decodeConverter: { self.isGreat = ($0 as? String == "Sure") }
              // The json will say 'Sure  if isGreat is true, otherwise it will say 'Nah'
              , encodeConverter: { return self.isGreat ? "Sure": "Nah"})
        ]
    }
}

编码和解码

您可以为对象中的多个或所有属性添加泛型编码或解码。这可用于例如 base64、unicode 和加密。以下是一个 base64 示例

class SimleEncodingDecodingObject : EVObject{
    var firstName: String?
    var lastName: String?
    var street: String?
    var city: String?

    override func decodePropertyValue(value: Any, key: String) -> Any? {
        return (value as? String)?.base64Decoded?.string ?? value
    }

    override func encodePropertyValue(value: Any, key: String) -> Any {
        return (value as? String)?.base64Encoded.string ?? value
    }
}


extension String {
var data:          Data  { return Data(utf8) }
var base64Encoded: Data  { return data.base64EncodedData() }
var base64Decoded: Data? { return Data(base64Encoded: self) }
}

extension Data {
var string: String? { return String(data: self, encoding: .utf8) }
}

自定义对象转换器

如果您想将对象序列化为字典或 JSON,但其结构应与对象本身不同,则除了使用 propertyConverters 之外,您还可以通过实现 customConverter 函数来将整个对象进行转换。以下示例将整个对象序列化为字符串。您也可以返回表示自定义结构的字典,或者如果对象应该是一个数组,则返回一个数组

override func customConverter() -> AnyObject? {
    return "Object not serialized"
}

自定义类型转换器

如果您有需要特殊转换的自定义类型,则可以使用 EVCustomReflectable 协议扩展它。用于此目的的一个好实现可以找到在 Realm 子规格中针对 List 类型的实现。转换器实现如下

extension List : EVCustomReflectable {
    public func constructWith(value: Any?) -> EVCustomReflectable {
        if let array = value as? [NSDictionary] {
            self.removeAll()
            for dict in array {
                if let element: T = EVReflection.fromDictionary(dict, anyobjectTypeString: _rlmArray.objectClassName) as? T {
                    self.append(element)
                }
            }
        }
        return self
    }
    public func toCodableValue() -> Any {
        return self.enumerated().map { ($0.element as? EVReflectable)?.toDictionary() ?? NSDictionary() }
    }
}

有关使用方法,请查阅 Realm 单元测试

跳过特定值的序列化或反序列化

当需要不序列化或反序列化特定值时(如 nil、NSNull 或空字符串),您可以实现 skipPropertyValue 函数,如果该值需要跳过,则返回 true。有关此函数何时将被调用的信息,请参阅 转换选项

class TestObjectSkipValues: EVObject {
   var value1: String? 
   var value2: [String]?
   var value3: NSNumber?

   override func skipPropertyValue(value: Any, key: String) -> Bool {
      if let value = value as? String where value.characters.count == 0 || value == "null" {
         print("Ignoring empty string for key \(key)")
         return true
      } else if let value = value as? NSArray where value.count == 0 {
         print("Ignoring empty NSArray for key\(key)")
         return true
      } else if value is NSNull {
         print("Ignoring NSNull for key \(key)")
         return true
      }
      return false
   }
}

属性验证器

在设置值之前,值始终会使用标准的 validateValue KVO 函数进行验证。这意味着您也可以为每个属性创建一个验证函数。以下示例显示了为 name 属性创建的 validateName 函数。

enum MyValidationError: ErrorType {
   case TypeError,
   LengthError
}

public class GameUser: EVObject {
   var name: String?
   var memberSince: NSDate?
   var objectIsNotAValue: TestObject?

   func validateName(value:AutoreleasingUnsafeMutablePointer<AnyObject?>) throws {
      if let theValue = value.memory as? String {
         if theValue.lengthOfBytesUsingEncoding(NSUTF8StringEncoding) < 3 {
            NSLog("Validating name is not long enough \(theValue)")
            throw MyValidationError.LengthError
         }
         NSLog("Validating name OK \(theValue)")
      } else {
         NSLog("Validating name is not a string: \(value.memory)")
         throw MyValidationError.TypeError
     }
   }
}

打印选项

您应该能够通过将JSON解析为对象来解决所有问题。如果您收到警告,而且您知道它们无关紧要,并希望停止打印它们,可以通过调用以下代码行来抑制所有打印警告:

PrintOptions.Active = .None

如果您想打开打印输出,只需调用:

PrintOptions.Active = .All

还可以启用特定警告类型的打印。下面是与设置为 .All 等效的代码行。只需省略您想抑制的类型。

PrintOptions.Active = [.UnknownKeypath, .IncorrectKey, .ShouldExtendNSObject, .IsInvalidJson, .MissingProtocol, .MissingKey, .InvalidType, .InvalidValue, .InvalidClass, .EnumWithoutAssociatedValue]

反序列化类级别验证

在反序列化为对象时,还支持类级别的验证。有辅助函数用于将键设置为必填项或禁止。您还可以添加自定义消息。以下是有关如何实现此类验证的示例代码

public class ValidateObject: EVObject {
   var requiredKey1: String?
   var requiredKey2: String?
   var optionalKey1: String?

   override public func initValidation(dict: NSDictionary) {
      self.initMayNotContainKeys(["error"], dict: dict)
      self.initMustContainKeys(["requiredKey1", "requiredKey2"], dict: dict)
      if dict.valueForKey("requiredKey1") as? String == dict.valueForKey("optionalKey1") as? String {
         // this could also be called in your property specific validators
         self.addStatusMessage(.Custom, message: "optionalKey1 should not be the same as requiredKey1")
      }
   }
}

您可以使用类似以下代码的代码测试此验证:

func testValidation() {
   // Test missing required key
   let json = "{\"requiredKey1\": \"Value1\"}"
   let test = ValidateObject(json: json)
   XCTAssertNotEqual(test.evReflectionStatus(), .None, "We should have a not .None status")
   XCTAssertEqual(test.evReflectionStatuses.count, 1, "We should have 1 validation result")
   for (status, message) in test.evReflectionStatuses {
      print("Validation result: Status = \(status), Message = \(message)")
   }
}

当您使用对象继承时,应该怎么做

您可以将JSON反序列化为使用继承的对象。当将属性指定为基类时,则函数 getSpecificType 将返回正确的特定对象类型。请参阅以下示例代码或 EVReflectionInheritanceTests.swift 中的单元测试

class Quz: EVObject {
    var fooArray: Array<Foo> = []
    var fooBar: Foo?
    var fooBaz: Foo?
}

class Foo: EVObject {
    var allFoo: String = "all Foo"

    // What you need to do to get the correct type for when you deserialize inherited classes
    override func getSpecificType(_ dict: NSDictionary) -> EVReflectable {
        if dict["justBar"] != nil {
            return Bar()
        } else if dict["justBaz"] != nil {
            return Baz()
        }
        return self
    }
}

class Bar : Foo {
    var justBar: String = "For bar only"
}

class Baz: Foo {
    var justBaz: String = "For baz only"
}

已知问题

EVReflection正在尝试处理所有类型。在Swift中,对于某些类型存在限制。到目前为止,对于这些限制中的任何一个都有解决方案。以下是概览:

在Swift中无法使用 .setObjectForKey 为:

  • 可为空类型字段如Int?
  • 基于枚举的属性
  • 可为空对象的数组如[MyObject?]
  • 集合如Set
  • 泛型属性如var myVal:T = T()
  • 结构体如CGRect或CGPoint

对于所有这些问题,都有解决方案。最简单的解决方案是使用不同的类型,例如:

  • 而不是Int?,您可以使用NSNumber?
  • 而不是[MyObject?],请使用[MyObject]
  • 而不是Set,请使用[MyObject]
  • 而不是 'var status: StatysType',请使用 'var status:Int' 并保存原始值
  • 而不是泛型属性,使用可以存储数据的特定属性(例如字典)
  • 而不是使用结构体,为该结构体创建自己的对象模型

如果您想继续使用同一类型,可以在对象本身中覆盖setValue forUndefinedKey。请参阅WorkaroundsTests.swift和WorkaroundSwiftGenericsTests.swift,以查看所有这些类型的解决方案。

泛型属性

对于泛型属性,需要EVGenericsKVC协议。请参阅WorkaroundSwiftGenericsTests.swift

包含可为空对象的数组或集合

对于包含可为空对象或集合的数组,如[MyObj?]或Set,则需要EVArrayConvertable协议。请参阅WorkaroundsTests.swift

Swift字典

对于Swift字典(而非NSDictionary),需要EVDictionaryConvertable协议。请参阅WorkaroundsTests.swift

许可

EVReflection在MIT 3许可证下可用。有关更多信息,请参阅LICENSE文件。

我的其他库

请参阅我的其他公开iOS库

  • EVReflection - 基于反射(字典、CKRecord、JSON和XML)的对象映射,具有阿尔法与Moya的扩展以及RxSwift或ReactiveSwift
  • EVCloudKitDao - 简化对苹果云服务(CloudKit)的访问
  • EVFaceTracker - 根据您的面部距离和角度,计算您的设备与面部之间的关系,以模拟3D效果
  • EVURLCache - 一个NSURLCache子类,用于处理所有使用NSURLRequest的Web请求
  • AlamofireOauth2 - 使用Alamofire实现OAuth2的Swift实现
  • EVWordPressAPI - 使用AlamofireOauth2、AlomofireJsonToObjects和EVReflection(正在进行中)实现的WordPress(Jetpack)API的Swift实现
  • PassportScanner - 扫描护照MRZ代码,并提取名字、姓氏、护照号码、国籍、出生日期、到期日期和个人编号。
  • AttributedTextView - 以支持多个链接(URL、哈希标记、提及)的方式创建最简单的属性文本视图

EVReflection (Gource可视化) 的演变

Evolution of EVReflection (Gource Visualization)