成功将 KVC 与 Swift Structs 结合使用

Successfully using KVC with Swift Structs

提问人:CentrumGuy 提问时间:10/16/2023 最后编辑:CentrumGuy 更新时间:10/16/2023 访问量:77

问:

我面临着一个非常小众的问题,但希望有人可以帮助我。我在我的项目中使用 KVC 来设置类中的值。此类包含属性,包括一些结构。我能够使用该协议使这些结构兼容(我知道这并不理想,但这是我为此找到的最佳方法)。现在,当我尝试在父类上使用为结构中的一个属性设置值时,它不起作用。经过一些调试,我了解到它将结构桥接到其 Objective-C 等效类并在该 Objective-C 等效类上设置值,但它不会将 Objective-C 类转换回结构并更新父类上的结构。@objc_ObjectiveCBridgeablesetValue(_:forKeyPath:)

我认为这应该是可能的,如 https://stackoverflow.com/questions/23216673/how-can-i-animate-the-height-of-a-calayer-with-the-origin-coming-from-the-bottom#:~:text=Then%20for%20the%20animation%2C%20you%20simply%20animate%20the%20bounds%20(or%20even%20better%2C%20you%20can%20animate%20%22bounds.size.height%22%2C%20since%20only%20the%20height%20is%20changing)%3A中所说的您可以进行动画处理,您可以在bounds.size.heightCABasicAnimation

这是我的代码(可以在 Swift playground 中运行):

@available(iOS 2.0, *)
@objc public class TestClass: NSObject {
    @objc public var x: Int
    @objc public var y: Int
    @objc public var testStruct = TestStruct(z: 0)
    
    init(x:Int, y: Int) {
        self.x = x
        self.y = y
    }
}

@available(iOS 2.0, *)
public struct TestStruct: _ObjectiveCBridgeable {
    public typealias _ObjectiveCType = NSTestStruct
    
    public var z: Int
    
    public init(z: Int) { self.z = z }
    
    public func _bridgeToObjectiveC() -> NSTestStruct {
        return NSTestStruct(z: z)
    }
    public static func _forceBridgeFromObjectiveC(_ source: NSTestStruct, result: inout TestStruct?) {
        result = TestStruct(z: source.z)
    }
    public static func _unconditionallyBridgeFromObjectiveC(_ source: NSTestStruct?) -> TestStruct {
        return TestStruct(z: source?.z ?? 0)
    }
    public static func _conditionallyBridgeFromObjectiveC(_ source: NSTestStruct, result: inout TestStruct?) -> Bool {
        result = TestStruct(z: source.z)
        return true
    }
}

@available(iOS 2.0, *)
@objc public class NSTestStruct: NSObject {
    @objc public var z: Int // Updates to "12"
    @objc public init(z: Int) {
        self.z = z
    }
}

var testClass = TestClass(x: 0, y: 0)
testClass.setValue(12, forKeyPath: "testStruct.z")
print(testClass.testStruct.z) // Prints "0", expected "12"
swift objective-c 结构 kvc

评论

0赞 HangarRash 10/16/2023
你真的需要所有这些 Objective-C 包袱吗?Swift 有自己的 KVC 等价物,不需要扩展 NSObject,它既可以处理类,也可以处理结构,而不需要 Objective-C 的东西。请参阅 stackoverflow.com/a/52635420/20287183 并查看该答案中的选项 #2 以获取示例。
0赞 CentrumGuy 10/16/2023
swift KVC 仅适用于文字 keyPaths,而我需要能够使用 String keyPaths
0赞 Martin R 10/16/2023
你能举个例子吗,你是如何“在CGRect上做同样的事情”的?
0赞 CentrumGuy 10/16/2023
我觉得很愚蠢,因为你是对的......它不适用于 CGRect(更新了我的问题)。我之前让它“工作”的代码是错误的。然而,类似的事情可以在动画中完成,如 stackoverflow.com/questions/23216673/ 所示......
2赞 Martin R 10/16/2023
@CentrumGuy:这之所以有效,是因为 CALayer 有一个特殊的实现,请参阅 stackoverflow.com/q/48964282/1187415setValue(forKeyPath:)

答:

1赞 CentrumGuy 10/16/2023 #1

正如 Martin R 所提到的,CABasicAnimation 有一个自定义,允许您设置嵌套属性。我编写了一个自定义程序,它对包含结构体的通用 keyPath 执行相同的操作,这些结构体的类型可直接转换为 Objective-C(相同的属性名称和类型)。这是我的解决方案:setValue(_:forKeyPath:)setValue(_:forKeyPath:)

public extension NSObject {
    func setValueRecursively(_ value: Any?, forKeyPath keyPath: String) {
        var currentKeyPath = keyPath
        var currentValue: Any? = value
        while true {
            guard let lastPeriodIndex = currentKeyPath.lastIndex(of: ".") else {
                self.setValue(currentValue, forKey: currentKeyPath)
                return
            }
            
            let parentKeyPath = String(currentKeyPath[currentKeyPath.startIndex ..< lastPeriodIndex])
            guard let parentValue = self.value(forKeyPath: parentKeyPath) as? NSObject else { return }
            parentValue.setValue(currentValue, forKey: String(currentKeyPath.suffix(currentKeyPath.count-parentKeyPath.count-1)))
            currentValue = parentValue
            currentKeyPath = parentKeyPath
        }
    }
}

使用与问题相同的代码,

testClass.setValueRecursively(12, forKeyPath: "testStruct.z")

按预期工作:)

注意:这不适用于 、 和表示为CGSizeCGRectNSValue