Using reflection to set object properties without using setValue forKey

后端 未结 2 1340
悲&欢浪女
悲&欢浪女 2021-02-01 06:36

In Swift it\'s not possible use .setValue(..., forKey: ...)

  • nullable type fields like Int?
  • properties that have an enum
2条回答
  •  清歌不尽
    2021-02-01 06:48

    I found a way around this when I was looking to solve a similar problem - that KVO can't set the value of a pure Swift protocol field. The protocol has to be marked @objc, which caused too much pain in my code base. The workaround is to look up the Ivar using the objective C runtime, get the field offset, and set the value using a pointer. This code works in a playground in 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(pointerToInstance + fieldOffset)
    
    // Set the value using the pointer
    pointerToField.memory = 42
    
    assert(instance.myInt == 42)
    

    Notes:

    • This is probably pretty fragile, you really shouldn't use this.
    • But maybe it could live in a thoroughly tested and updated reflection library until Swift gets a proper reflection API.
    • It's not that far away from what Mirror does internally, see the code in Reflection.mm, around here: https://github.com/apple/swift/blob/swift-2.2-branch/stdlib/public/runtime/Reflection.mm#L719
    • The same technique applies to the other types that KVO rejects, but you need to be careful to use the right UnsafeMutablePointer type. Particularly with protocol vars, which are 40 or 16 bytes, unlike a simple class optional which is 8 bytes (64 bit). See Mike Ash on the topic of Swift memory layout: https://mikeash.com/pyblog/friday-qa-2014-08-01-exploring-swift-memory-layout-part-ii.html

    Edit: There is now a framework called Runtime at https://github.com/wickwirew/Runtime which provides a pure Swift model of the Swift 4+ memory layout, allowing it to safely calculate the equivalent of ivar_getOffset without invoking the Obj C runtime. This allows setting properties like this:

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

    This is probably a good way forward until the equivalent capability becomes part of Swift itself.

提交回复
热议问题