【问题标题】:Using reflection to set object properties without using setValue forKey使用反射设置对象属性而不使用 setValue forKey
【发布时间】:2015-10-13 21:06:13
【问题描述】:

在 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.")
        }
    }
}

【问题讨论】:

  • 我可以建议您等到 Apple 在秋季发布 Swift 的源代码,因为他们知道如何遍历 Swift 属性。 (反射函数不仅返回 MirrorType 对象的副本,而且还引用每个属性),所以如果 MirrorType 将使其成为开源代码部分,那么您就可以看到它们如何实现这一点并将该方法移植到您的库中。
  • 好吧,我可以得到这些值。现在我想设置值
  • 没有镜像类型你能得到它们吗?
  • 您确实需要使用 reflect(..) 获取 MirrorType 参见底部的 valueForAny 方法:github.com/evermeer/EVReflection/blob/master/EVReflection/pod/…
  • 这就是我所说的。你只能通过reflect函数和MirrorType来获取值,但你不知道Apple在后台是如何做到的。他们可以在运行时以某种方式迭代属性,在他们发布源代码之前我们不知道如何。

标签: swift reflection setvalue


【解决方案1】:

不幸的是,这在 Swift 中是不可能的。

KVC 是一个 Objective-C 的东西。纯 Swift 选项(Int 和 Optional 的组合)不适用于 KVC。用Int? 做的最好的事情就是用NSNumber? 替换,这样KVC 就可以工作了。这是因为NSNumber 仍然是一个Objective-C 类。这是类型系统的一个可悲的限制。

不过,对于您的枚举,仍有希望。但是,这不会减少您必须执行的编码量,但它更简洁,并且在最好的情况下模仿了 KVC。

  1. 创建一个名为Settable的协议

    protocol Settable {
       mutating func setValue(value:String)
    }
    
  2. 让你的枚举确认协议

    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")
            }
       }
    }
    
  3. 创建方法: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)")
       }
    }
    
  4. 您现在可以拨打self.setEnumValue(".FirstType",forKey:"types")

【讨论】:

  • 我希望有一种解决方法,例如使用 performSelector 进行设置。我不确定属性是否像在 Objective C 中那样具有 get 和 set 方法调用。我尝试了很多签名但没有成功。我已经使用 if myObject.respondsToSelector(NSSelectorFromString("setMyProperty")) { 进行了很多测试
  • Swift 中的 Getter 和 setter 非常不同。吸气剂没有“选择器”。更何况,也没有performSelector
  • 在 swift 2 中 performSelector 又回来了,现在你可以使用类似的东西:NSTimer.scheduledTimerWithTimeInterval(0.001, target: myObject, selector: Selector(sel), userInfo: [3], repeats: false)或 NSThread.detachNewThreadSelector(Selector("myMethod:"), toTarget:myObject, withObject: "myValue") })
  • 你确定吗?我刚刚启动了我的 Xcode 7 Beta,却发现没有 performSelector。
  • 是的,我确定,这里是文档:xcdoc://?url=developer.apple.com/library/etc/redirect/xcode/ios/1048/documentation/Cocoa/Reference/基础/协议/NSObject_Protocol/index.html
【解决方案2】:

当我想解决一个类似的问题时,我找到了一种解决方法——KVO 无法设置纯 Swift 协议字段的值。该协议必须标记为@objc,这对我的代码库造成了太大的痛苦。 解决方法是使用目标 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)

注意事项:

编辑:现在https://github.com/wickwirew/Runtime 有一个名为 Runtime 的框架,它提供了 Swift 4+ 内存布局的纯 Swift 模型,允许它在不调用 Obj 的情况下安全地计算 ivar_getOffset 的等价物C 运行时。这允许像这样设置属性:

let info = try typeInfo(of: User.self)
let property = try info.property(named: "username")
try property.set(value: "newUsername", on: &user)

在等效功能成为 Swift 本身的一部分之前,这可能是一个很好的前进方向。

【讨论】:

  • 非常酷的代码!你是对的,这是危险的代码。我仍然会玩这个来看看它的潜力。
  • 效果很好!!!而且我发现了一些有趣的东西(绝对不应该在生产中使用):似乎您可以仅使用 UnsafeMutablePointer (而不是 UnsafeMutablePointer 在您的代码中)创建任何类型的映射器。完全不安全但很酷
猜你喜欢
  • 2015-01-17
  • 2010-10-11
  • 1970-01-01
  • 1970-01-01
  • 2012-05-10
  • 1970-01-01
  • 1970-01-01
  • 2010-10-26
相关资源
最近更新 更多