使用反射设置对象属性而不使用 setValue forKey
Using reflection to set object properties without using setValue forKey
在 Swift 中无法使用 .setValue(..., forKey: ...)
- 像
Int
这样的可空类型字段?
- 具有
enum
类型的属性
- 可空对象数组,如
[MyObject?]
对此有一种解决方法,即覆盖对象本身的 setValue forUndefinedKey 方法。
因为我正在编写一个基于反射的通用对象映射器。参见 EVReflection 我想尽可能减少这种手动映射。
是否有其他方法可以自动设置这些属性?
可以在我的库中的单元测试中找到解决方法 here
这是代码:
class WorkaroundsTests: XCTestCase {
func testWorkarounds() {
let json:String = "{\"nullableType\": 1,\"status\": 0, \"list\": [ {\"nullableType\": 2}, {\"nullableType\": 3}] }"
let status = Testobject(json: json)
XCTAssertTrue(status.nullableType == 1, "the nullableType should be 1")
XCTAssertTrue(status.status == .NotOK, "the status should be NotOK")
XCTAssertTrue(status.list.count == 2, "the list should have 2 items")
if status.list.count == 2 {
XCTAssertTrue(status.list[0]?.nullableType == 2, "the first item in the list should have nullableType 2")
XCTAssertTrue(status.list[1]?.nullableType == 3, "the second item in the list should have nullableType 3")
}
}
}
class Testobject: EVObject {
enum StatusType: Int {
case NotOK = 0
case OK
}
var nullableType: Int?
var status: StatusType = .OK
var list: [Testobject?] = []
override func setValue(value: AnyObject!, forUndefinedKey key: String) {
switch key {
case "nullableType":
nullableType = value as? Int
case "status":
if let rawValue = value as? Int {
status = StatusType(rawValue: rawValue)!
}
case "list":
if let list = value as? NSArray {
self.list = []
for item in list {
self.list.append(item as? Testobject)
}
}
default:
NSLog("---> setValue for key '\(key)' should be handled.")
}
}
}
不幸的是,这在 Swift 中是不可能的。
KVC 是 Objective-C 的东西。纯 Swift 可选值(Int 和 Optional 的组合)不适用于 KVC。使用 Int?
的最佳做法是用 NSNumber?
替换,这样 KVC 就可以工作了。这是因为 NSNumber
仍然是 Objective-C class。这是类型系统的一个可悲的限制。
尽管如此,对于您的枚举,仍有希望。但是,这不会减少您必须执行的编码量,但它更简洁,并且在最好的情况下模仿了 KVC。
创建一个名为 Settable
的协议
protocol Settable {
mutating func setValue(value:String)
}
让您的枚举确认协议
enum Types : Settable {
case FirstType, SecondType, ThirdType
mutating func setValue(value: String) {
if value == ".FirstType" {
self = .FirstType
} else if value == ".SecondType" {
self = .SecondType
} else if value == ".ThirdType" {
self = .ThirdType
} else {
fatalError("The value \(value) is not settable to this enum")
}
}
}
创建方法:setEnumValue(value:value, forKey key:Any)
setEnumValue(value:String forKey key:Any) {
if key == "types" {
self.types.setValue(value)
} else {
fatalError("No variable found with name \(key)")
}
}
- 您现在可以调用
self.setEnumValue(".FirstType",forKey:"types")
当我试图解决一个类似的问题时,我找到了解决这个问题的方法——KVO 无法设置纯 Swift 协议字段的值。该协议必须标记为@objc,这给我的代码库带来了太多痛苦。
解决方法是使用 objective C 运行时查找 Ivar,获取字段偏移量,然后使用指针设置值。
此代码适用于 Swift 2.2:
的游乐场
import Foundation
class MyClass
{
var myInt: Int?
}
let instance = MyClass()
// Look up the ivar, and it's offset
let ivar: Ivar = class_getInstanceVariable(instance.dynamicType, "myInt")
let fieldOffset = ivar_getOffset(ivar)
// Pointer arithmetic to get a pointer to the field
let pointerToInstance = unsafeAddressOf(instance)
let pointerToField = UnsafeMutablePointer<Int?>(pointerToInstance + fieldOffset)
// Set the value using the pointer
pointerToField.memory = 42
assert(instance.myInt == 42)
备注:
- 这可能非常脆弱,您真的不应该使用它。
- 但也许它可以存在于经过全面测试和更新的反射库中,直到 Swift 获得适当的反射 API。
- 这与 Mirror 内部所做的相去不远,请参阅 Reflection.mm 中的代码,大约在此处:https://github.com/apple/swift/blob/swift-2.2-branch/stdlib/public/runtime/Reflection.mm#L719
- 同样的技术适用于 KVO 拒绝的其他类型,但您需要小心使用正确的 UnsafeMutablePointer 类型。特别是对于 40 或 16 字节的协议变量,不像简单的 class 可选的 8 字节(64 位)。请参阅 Mike Ash 关于 Swift 内存布局的主题:https://mikeash.com/pyblog/friday-qa-2014-08-01-exploring-swift-memory-layout-part-ii.html
编辑: https://github.com/wickwirew/Runtime 现在有一个名为 Runtime 的框架,它提供 Swift 4+ 内存的纯 Swift 模型布局,允许它安全地计算 ivar_getOffset 的等价物,而无需调用 Obj C 运行时。这允许设置如下属性:
let info = try typeInfo(of: User.self)
let property = try info.property(named: "username")
try property.set(value: "newUsername", on: &user)
在等效功能成为 Swift 本身的一部分之前,这可能是一个很好的前进方式。
Swift 5
要使用纯 swift 类型设置和获取属性值,您可以使用带有共享函数的内部 ReflectionMirror.swift 方法:
swift_reflectionMirror_recursiveCount
swift_reflectionMirror_recursiveChildMetadata
swift_reflectionMirror_recursiveChildOffset
我们的想法是获取有关对象的每个 属性 的信息,然后通过其指针偏移将值设置为所需的值。
有 KeyValueCoding
协议的示例代码 Swift 实现 setValue(_ value: Any?, forKey key: String)
方法:
typealias NameFreeFunc = @convention(c) (UnsafePointer<CChar>?) -> Void
struct FieldReflectionMetadata {
let name: UnsafePointer<CChar>? = nil
let freeFunc: NameFreeFunc? = nil
let isStrong: Bool = false
let isVar: Bool = false
}
@_silgen_name("swift_reflectionMirror_recursiveCount")
fileprivate func swift_reflectionMirror_recursiveCount(_: Any.Type) -> Int
@_silgen_name("swift_reflectionMirror_recursiveChildMetadata")
fileprivate func swift_reflectionMirror_recursiveChildMetadata(
_: Any.Type
, index: Int
, fieldMetadata: UnsafeMutablePointer<FieldReflectionMetadata>
) -> Any.Type
@_silgen_name("swift_reflectionMirror_recursiveChildOffset")
fileprivate func swift_reflectionMirror_recursiveChildOffset(_: Any.Type, index: Int) -> Int
protocol Accessors {}
extension Accessors {
static func set(value: Any?, pointer: UnsafeMutableRawPointer) {
if let value = value as? Self {
pointer.assumingMemoryBound(to: self).pointee = value
}
}
}
struct ProtocolTypeContainer {
let type: Any.Type
let witnessTable = 0
var accessors: Accessors.Type {
unsafeBitCast(self, to: Accessors.Type.self)
}
}
protocol KeyValueCoding {
}
extension KeyValueCoding {
private mutating func withPointer<Result>(displayStyle: Mirror.DisplayStyle, _ body: (UnsafeMutableRawPointer) throws -> Result) throws -> Result {
switch displayStyle {
case .struct:
return try withUnsafePointer(to: &self) {
let pointer = UnsafeMutableRawPointer(mutating: [=10=])
return try body(pointer)
}
case .class:
return try withUnsafePointer(to: &self) {
try [=10=].withMemoryRebound(to: UnsafeMutableRawPointer.self, capacity: 1) {
try body([=10=].pointee)
}
}
default:
fatalError("Unsupported type")
}
}
public mutating func setValue(_ value: Any?, forKey key: String) {
let mirror = Mirror(reflecting: self)
guard let displayStyle = mirror.displayStyle
, displayStyle == .class || displayStyle == .struct
else {
return
}
let type = type(of: self)
let count = swift_reflectionMirror_recursiveCount(type)
for i in 0..<count {
var field = FieldReflectionMetadata()
let childType = swift_reflectionMirror_recursiveChildMetadata(type, index: i, fieldMetadata: &field)
defer { field.freeFunc?(field.name) }
guard let name = field.name.flatMap({ String(validatingUTF8: [=10=]) }),
name == key
else {
continue
}
let clildOffset = swift_reflectionMirror_recursiveChildOffset(type, index: i)
try? withPointer(displayStyle: displayStyle) { pointer in
let valuePointer = pointer.advanced(by: clildOffset)
let container = ProtocolTypeContainer(type: childType)
container.accessors.set(value: value, pointer: valuePointer)
}
break
}
}
}
此方法适用于 class
和 struct
,并支持可选、枚举和继承(对于 类)属性:
// Class
enum UserType {
case admin
case guest
case none
}
class User: KeyValueCoding {
let id = 0
let name = "John"
let birthday: Date? = nil
let type: UserType = .none
}
var user = User()
user.setValue(12345, forKey: "id")
user.setValue("Bob", forKey: "name")
user.setValue(Date(), forKey: "birthday")
user.setValue(UserType.admin, forKey: "type")
print(user.id, user.name, user.birthday!, user.type)
// Outputs: 12345 Bob 2022-04-22 10:41:10 +0000 admin
// Struct
struct Book: KeyValueCoding {
let id = 0
let title = "Swift"
let info: String? = nil
}
var book = Book()
book.setValue(56789, forKey: "id")
book.setValue("ObjC", forKey: "title")
book.setValue("Development", forKey: "info")
print(book.id, book.title, book.info!)
// Outputs: 56789 ObjC Development
如果您害怕将 @_silgen_name
用于共享功能,您可以使用 dlsym
动态访问它,例如:dlsym(RTLD_DEFAULT, "swift_reflectionMirror_recursiveCount")
等
更新
有一个 swift 包 (https://github.com/ikhvorost/KeyValueCoding) 完全实现了纯 Swift 的 KeyValueCoding
协议,它支持: get/set 任何值属性 通过键、下标、获取元数据类型、属性列表等。
在 Swift 中无法使用 .setValue(..., forKey: ...)
- 像
Int
这样的可空类型字段? - 具有
enum
类型的属性 - 可空对象数组,如
[MyObject?]
对此有一种解决方法,即覆盖对象本身的 setValue forUndefinedKey 方法。
因为我正在编写一个基于反射的通用对象映射器。参见 EVReflection 我想尽可能减少这种手动映射。
是否有其他方法可以自动设置这些属性?
可以在我的库中的单元测试中找到解决方法 here 这是代码:
class WorkaroundsTests: XCTestCase {
func testWorkarounds() {
let json:String = "{\"nullableType\": 1,\"status\": 0, \"list\": [ {\"nullableType\": 2}, {\"nullableType\": 3}] }"
let status = Testobject(json: json)
XCTAssertTrue(status.nullableType == 1, "the nullableType should be 1")
XCTAssertTrue(status.status == .NotOK, "the status should be NotOK")
XCTAssertTrue(status.list.count == 2, "the list should have 2 items")
if status.list.count == 2 {
XCTAssertTrue(status.list[0]?.nullableType == 2, "the first item in the list should have nullableType 2")
XCTAssertTrue(status.list[1]?.nullableType == 3, "the second item in the list should have nullableType 3")
}
}
}
class Testobject: EVObject {
enum StatusType: Int {
case NotOK = 0
case OK
}
var nullableType: Int?
var status: StatusType = .OK
var list: [Testobject?] = []
override func setValue(value: AnyObject!, forUndefinedKey key: String) {
switch key {
case "nullableType":
nullableType = value as? Int
case "status":
if let rawValue = value as? Int {
status = StatusType(rawValue: rawValue)!
}
case "list":
if let list = value as? NSArray {
self.list = []
for item in list {
self.list.append(item as? Testobject)
}
}
default:
NSLog("---> setValue for key '\(key)' should be handled.")
}
}
}
不幸的是,这在 Swift 中是不可能的。
KVC 是 Objective-C 的东西。纯 Swift 可选值(Int 和 Optional 的组合)不适用于 KVC。使用 Int?
的最佳做法是用 NSNumber?
替换,这样 KVC 就可以工作了。这是因为 NSNumber
仍然是 Objective-C class。这是类型系统的一个可悲的限制。
尽管如此,对于您的枚举,仍有希望。但是,这不会减少您必须执行的编码量,但它更简洁,并且在最好的情况下模仿了 KVC。
创建一个名为
的协议Settable
protocol Settable { mutating func setValue(value:String) }
让您的枚举确认协议
enum Types : Settable { case FirstType, SecondType, ThirdType mutating func setValue(value: String) { if value == ".FirstType" { self = .FirstType } else if value == ".SecondType" { self = .SecondType } else if value == ".ThirdType" { self = .ThirdType } else { fatalError("The value \(value) is not settable to this enum") } } }
创建方法:
setEnumValue(value:value, forKey key:Any)
setEnumValue(value:String forKey key:Any) { if key == "types" { self.types.setValue(value) } else { fatalError("No variable found with name \(key)") } }
- 您现在可以调用
self.setEnumValue(".FirstType",forKey:"types")
当我试图解决一个类似的问题时,我找到了解决这个问题的方法——KVO 无法设置纯 Swift 协议字段的值。该协议必须标记为@objc,这给我的代码库带来了太多痛苦。 解决方法是使用 objective C 运行时查找 Ivar,获取字段偏移量,然后使用指针设置值。 此代码适用于 Swift 2.2:
的游乐场import Foundation
class MyClass
{
var myInt: Int?
}
let instance = MyClass()
// Look up the ivar, and it's offset
let ivar: Ivar = class_getInstanceVariable(instance.dynamicType, "myInt")
let fieldOffset = ivar_getOffset(ivar)
// Pointer arithmetic to get a pointer to the field
let pointerToInstance = unsafeAddressOf(instance)
let pointerToField = UnsafeMutablePointer<Int?>(pointerToInstance + fieldOffset)
// Set the value using the pointer
pointerToField.memory = 42
assert(instance.myInt == 42)
备注:
- 这可能非常脆弱,您真的不应该使用它。
- 但也许它可以存在于经过全面测试和更新的反射库中,直到 Swift 获得适当的反射 API。
- 这与 Mirror 内部所做的相去不远,请参阅 Reflection.mm 中的代码,大约在此处:https://github.com/apple/swift/blob/swift-2.2-branch/stdlib/public/runtime/Reflection.mm#L719
- 同样的技术适用于 KVO 拒绝的其他类型,但您需要小心使用正确的 UnsafeMutablePointer 类型。特别是对于 40 或 16 字节的协议变量,不像简单的 class 可选的 8 字节(64 位)。请参阅 Mike Ash 关于 Swift 内存布局的主题:https://mikeash.com/pyblog/friday-qa-2014-08-01-exploring-swift-memory-layout-part-ii.html
编辑: https://github.com/wickwirew/Runtime 现在有一个名为 Runtime 的框架,它提供 Swift 4+ 内存的纯 Swift 模型布局,允许它安全地计算 ivar_getOffset 的等价物,而无需调用 Obj C 运行时。这允许设置如下属性:
let info = try typeInfo(of: User.self)
let property = try info.property(named: "username")
try property.set(value: "newUsername", on: &user)
在等效功能成为 Swift 本身的一部分之前,这可能是一个很好的前进方式。
Swift 5
要使用纯 swift 类型设置和获取属性值,您可以使用带有共享函数的内部 ReflectionMirror.swift 方法:
swift_reflectionMirror_recursiveCount
swift_reflectionMirror_recursiveChildMetadata
swift_reflectionMirror_recursiveChildOffset
我们的想法是获取有关对象的每个 属性 的信息,然后通过其指针偏移将值设置为所需的值。
有 KeyValueCoding
协议的示例代码 Swift 实现 setValue(_ value: Any?, forKey key: String)
方法:
typealias NameFreeFunc = @convention(c) (UnsafePointer<CChar>?) -> Void
struct FieldReflectionMetadata {
let name: UnsafePointer<CChar>? = nil
let freeFunc: NameFreeFunc? = nil
let isStrong: Bool = false
let isVar: Bool = false
}
@_silgen_name("swift_reflectionMirror_recursiveCount")
fileprivate func swift_reflectionMirror_recursiveCount(_: Any.Type) -> Int
@_silgen_name("swift_reflectionMirror_recursiveChildMetadata")
fileprivate func swift_reflectionMirror_recursiveChildMetadata(
_: Any.Type
, index: Int
, fieldMetadata: UnsafeMutablePointer<FieldReflectionMetadata>
) -> Any.Type
@_silgen_name("swift_reflectionMirror_recursiveChildOffset")
fileprivate func swift_reflectionMirror_recursiveChildOffset(_: Any.Type, index: Int) -> Int
protocol Accessors {}
extension Accessors {
static func set(value: Any?, pointer: UnsafeMutableRawPointer) {
if let value = value as? Self {
pointer.assumingMemoryBound(to: self).pointee = value
}
}
}
struct ProtocolTypeContainer {
let type: Any.Type
let witnessTable = 0
var accessors: Accessors.Type {
unsafeBitCast(self, to: Accessors.Type.self)
}
}
protocol KeyValueCoding {
}
extension KeyValueCoding {
private mutating func withPointer<Result>(displayStyle: Mirror.DisplayStyle, _ body: (UnsafeMutableRawPointer) throws -> Result) throws -> Result {
switch displayStyle {
case .struct:
return try withUnsafePointer(to: &self) {
let pointer = UnsafeMutableRawPointer(mutating: [=10=])
return try body(pointer)
}
case .class:
return try withUnsafePointer(to: &self) {
try [=10=].withMemoryRebound(to: UnsafeMutableRawPointer.self, capacity: 1) {
try body([=10=].pointee)
}
}
default:
fatalError("Unsupported type")
}
}
public mutating func setValue(_ value: Any?, forKey key: String) {
let mirror = Mirror(reflecting: self)
guard let displayStyle = mirror.displayStyle
, displayStyle == .class || displayStyle == .struct
else {
return
}
let type = type(of: self)
let count = swift_reflectionMirror_recursiveCount(type)
for i in 0..<count {
var field = FieldReflectionMetadata()
let childType = swift_reflectionMirror_recursiveChildMetadata(type, index: i, fieldMetadata: &field)
defer { field.freeFunc?(field.name) }
guard let name = field.name.flatMap({ String(validatingUTF8: [=10=]) }),
name == key
else {
continue
}
let clildOffset = swift_reflectionMirror_recursiveChildOffset(type, index: i)
try? withPointer(displayStyle: displayStyle) { pointer in
let valuePointer = pointer.advanced(by: clildOffset)
let container = ProtocolTypeContainer(type: childType)
container.accessors.set(value: value, pointer: valuePointer)
}
break
}
}
}
此方法适用于 class
和 struct
,并支持可选、枚举和继承(对于 类)属性:
// Class
enum UserType {
case admin
case guest
case none
}
class User: KeyValueCoding {
let id = 0
let name = "John"
let birthday: Date? = nil
let type: UserType = .none
}
var user = User()
user.setValue(12345, forKey: "id")
user.setValue("Bob", forKey: "name")
user.setValue(Date(), forKey: "birthday")
user.setValue(UserType.admin, forKey: "type")
print(user.id, user.name, user.birthday!, user.type)
// Outputs: 12345 Bob 2022-04-22 10:41:10 +0000 admin
// Struct
struct Book: KeyValueCoding {
let id = 0
let title = "Swift"
let info: String? = nil
}
var book = Book()
book.setValue(56789, forKey: "id")
book.setValue("ObjC", forKey: "title")
book.setValue("Development", forKey: "info")
print(book.id, book.title, book.info!)
// Outputs: 56789 ObjC Development
如果您害怕将 @_silgen_name
用于共享功能,您可以使用 dlsym
动态访问它,例如:dlsym(RTLD_DEFAULT, "swift_reflectionMirror_recursiveCount")
等
更新
有一个 swift 包 (https://github.com/ikhvorost/KeyValueCoding) 完全实现了纯 Swift 的 KeyValueCoding
协议,它支持: get/set 任何值属性 通过键、下标、获取元数据类型、属性列表等。