EVReflection
一般信息
目前,主分支与 Swift 4.2 和 5.0 beta 进行了测试。如果您想继续使用旧版本的 EVReflection,请使用相应的分支。运行单元测试以查看 EVReflection 的实际运行情况。
EVReflection 被用于 EVCloudKitDao 和 EVWordPressAPI
在大多数情况下,EVReflection 非常容易使用。只需看一下 易于使用部分。但如果您需要进行非标准的特定操作,EVReflection 将提供广泛的功能。
可用的扩展
有扩展可以让 EVReflection 与 XMLDictionairy、Realm、CloudKit、Alamofire 和 Moya 结合 RxSwift 或 ReactiveSwift 使用。
- XML
- CloudKit
- CoreData
- Realm
- Alamofire
- AlamofireXML
- Moya
- MoyaXML
- MoyaRxSwift
- MoyaRxSwiftXML
- MoyaReactiveSwift
- MoyaReactiveSwiftXML
所有这些扩展都可以通过在您的 podfile 中添加类似的内容进行安装
pod 'EVReflection/MoyaRxSwift'
对于 Carthage,目前还没有针对上述所有扩展的扩展。如有需要,请告知我。对于 Carthage,您可以使用
github "evermeer/EVReflection"
索引
- EVReflection 的主要功能
- 易于使用
- 如果您有 XML 而不是 JSON
- 在您的应用中使用 EVReflection
- 更多示例代码
- 扩展现有对象
- 转换选项
- 自动将 Swift 关键字进行关键字映射
- 自动将 PascalCase 或 camelCase 映射为 snake_case
- 自定义关键字映射
- 自定义属性转换器
- 自定义对象转换器
- 自定义类型转换器
- 编码和解码
- 跳过特定值的序列化或反序列化
- 属性验证器
- 打印选项
- 反序列化类级别验证
- 当您使用对象继承时要做什么
- 已知问题
- 许可证
- 我的其他库
EVReflection 的主要功能
- 根据 NSObject 遍历对象,并将其转换为字典,反之亦然。(也可参阅 XML 和 .plist 示例!)
- 将对象解析为 JSON 字符串,反之亦然。
- 支持 NSCoding 函数 encodeWithCoder 和 decodeObjectWithCoder
- 使用所有属性的同时支持 Protocol,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或更高版本
您只需将EVReflection添加到您的工作区,在您的Podfile中添加以下两行即可
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版本会将您使用的所有pods动态地制作成框架。因此,它只支持iOS 8.0或更高版本。当使用框架时,您还必须在swift文件的顶部添加一个导入,例如
import EVReflection
如果您想支持iOS 8.0之前的旧版本,那么您也可以将pod文件夹中的文件复制到您的项目中。您必须使用Swift2.3版本或更低版本。Swift 3从Swift 7中放弃了iOS 7支持。
请注意,当您在框架中而不是在主应用中定义对象时,您必须让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。这将使您能够将 EVReflection 的功能添加到需要其他框架的对象中。在某些情况下,您可能还需要一些额外的代码。请参阅 Realm 和 NSManagedObject 子规范以获取示例。扩展您的对象的最基本的办法如下
import EVReflection
extension MyObject : EVReflectable { }
额外信息
转换选项
使用 EVReflection 几乎任何函数,您都可以指定要使用的转换选项。这是通过选项集完成的。您可以使用以下转换选项
- 无 - 不使用任何转换函数。
- 如果指定,则 EVObject 将调用函数 propertyConverters
- 如果指定,则 EVObject 将调用函数 propertyMapping
- 如果指定,则 EVObject 将调用函数 skipPropertyValue
- 如果指定,则自动调用 pascalCase 和 snake_case 属性键映射。
- 如果想在编码值(如 base64、unicode、加密等)方面具有类级别功能,请参考
- 如果希望在对值进行解码(如 base64、unicode、加密等)方面具有类级别功能,请参考
在 EVReflection 中,所有函数都将使用特定于其功能的默认转换选项。以下为 4 种默认转换类型
- DefaultNSCoding = [无]
- DefaultComparing = [PropertyConverter, PropertyMapping, SkipPropertyValue]
- DefaultDeserialize = [PropertyConverter, PropertyMapping, SkipPropertyValue, KeyCleanup, Decoding]
- DefaultSerialize = [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", "列", "文件", "函数", "行", "关联性", "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,然后这些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协议。对于List类型的Realm subspec,有一个很好的实现。转换器是这样的实现。
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 unittest
跳过特定值的序列化或反序列化
当需要跳过特定值如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]
- 使用[MyObject]代替Set
- 使用'var status: StatysType'替换为'var status:Int'并保存rawValue
- 使用可以包含数据的特定属性(如字典)代替泛型属性
- 使用结构体而不是创建该结构体的自己的对象模型
如果您想继续使用相同的类型,可以在对象本身中重写setValue forUndefinedKey。请参阅WorkaroundsTests.swift和WorkaroundSwiftGenericsTests.swift,以查看处理所有这些类型的解决方案的实际效果。
泛型属性
对于泛型属性,需要协议EVGenericsKVC。请参阅WorkaroundsSwiftGenericsTests.swift
可空对象数组和集合
对于如[MyObj?]或Set的可空对象数组或集合,需要协议EVArrayConvertable。请参阅WorkaroundsTests.swift
Swift字典
对于 Swift 字典(而不是 NSDictionary),需要使用 EVDictionaryConvertable 协议。请参阅 WorkaroundsTests.swift
许可证
EVReflection 在 MIT 3 许可证下可用。有关更多信息,请参阅 LICENSE 文件。
我的其他库
还可以查看我的其他公开源代码 iOS 库
- EVReflection - 基于反射(字典、CKRecord、JSON 和 XML)的对象映射,并提供 Alamofire 和 Moya 扩展,支持 RxSwift 或 ReactiveSwift
- EVCloudKitDao - 简化了 Apple 的 CloudKit 访问
- EVFaceTracker - 计算你的设备相对于你的脸的距离和角度,以模拟 3D 效果
- EVURLCache - 一个适用于处理所有使用 NSURLRequest 的 web 请求的 NSURLCache 子类
- AlamofireOauth2 - 使用 Alamofire 的 OAuth2 的 swift 实现
- EVWordPressAPI - 使用 AlamofireOauth2、AlomofireJsonToObjects 和 EVReflection 实现的 WordPress API(工作中)
- PassportScanner - 扫描护照的 MRZ 代码,并提取姓名、护照号码、国籍、出生日期、到期日期和个人号码。
- AttributedTextView - 创建一个带有支持多个链接(url、标签、提及)的 attributed UITextView 的最简单方法