使用反射来设置对象属性而不使用setValue forKey [英] Using reflection to set object properties without using setValue forKey

查看:128
本文介绍了使用反射来设置对象属性而不使用setValue forKey的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在Swift中无法使用.setValue(..., forKey: ...)

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

  • Int这样的可空类型字段?
  • 类型为enum的属性
  • 可空对象的数组,例如[MyObject?]
  • nullable type fields like Int?
  • properties that have an enum as it's type
  • an Array of nullable objects like [MyObject?]

有一种解决方法,就是通过覆盖对象本身中的setValue forUndefinedKey方法.

There is one workaround for this and that is by overriding the setValue forUndefinedKey method in the object itself.

由于我正在基于反射写一个通用的对象映射器.请参见 EVReflection .我想尽可能地减少这种手动映射.

Since I'm writing a general object mapper based on reflection. See EVReflection I would like to minimize this kind of manual mapping as much as possible.

还有另一种自动设置这些属性的方法吗?

Is there an other way to set those properties automatically?

可以在我的库中的单元测试中找到解决方法此处 这是代码:

The workaround can be found in a unit test in my library here This is the code:

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

推荐答案

当我试图解决类似的问题时,我发现了一种解决方法-KVO无法设置纯Swift协议字段的值.该协议必须标记为@objc,这在我的代码库中造成了太多痛苦. 解决方法是使用目标C运行时查找Ivar,获取字段偏移量,并使用指针设置值. 这段代码可以在Swift 2.2的操场上工作:

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<Int?>(pointerToInstance + fieldOffset)

// Set the value using the pointer
pointerToField.memory = 42

assert(instance.myInt == 42)

注意:

  • 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

现在在 https://github上有一个名为Runtime的框架. com/wickwirew/Runtime 提供了Swift 4+内存布局的纯Swift模型,使它可以安全地计算等效于ivar_getOffset的等效项,而无需调用Obj C运行时.这样可以设置如下属性:

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)

在等效功能成为Swift本身的一部分之前,这可能是一个好方法.

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

这篇关于使用反射来设置对象属性而不使用setValue forKey的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆