目前,主分支已测试与 Swift 4.2 和 5.0 测试版兼容。如果您想在旧版本中继续使用 EVReflection,请使用相应的分支。运行单元测试以查看 EVReflection 的运行情况。
请现在更新到 cocoapods 1.7.0,可以使用以下方式
sudo gem install cocoapods --pre
这是为了支持除 5.0 之外的其他 Swift 版本所必须的库。有关更多信息,请参阅cocoapods 博客
EVReflection 用于 EVCloudKitDao 和 EVWordPressAPI
在大多数情况下,EVReflection 非常易于使用。只需查看它很容易使用部分。但如果您确实想做一些非常规的特殊事情,EVReflection 会提供广泛的功能。
有扩展可用于将 EVReflection 与 XMLDictionary、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
- 自定义关键字映射
- 自定义属性转换器
- 自定义对象转换器
- 自定义类型转换器
- 编码和解码
- 跳过特定值的序列化或反序列化
- 属性验证器
- 打印选项
- 反序列化类级别验证
- 当您使用对象继承时,应该怎么做
- 已知问题
- 许可
- 我的其他库
- 根据 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,那么您可以使用 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' 通过依赖关系管理器 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]
如果您的 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"
在从 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中,对于某些类型存在限制。到目前为止,对于这些限制中的任何一个都有解决方案。以下是概览:
- 可为空类型字段如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字典(而非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、哈希标记、提及)的方式创建最简单的属性文本视图