You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将AnyKeyPath转换为带可选类型的ReferenceWritableKeyPath?

背景情境

考虑以下类和KeyPath实例:

class Foo {
   var name: String? = ""
}

var foo = Foo()
let keyPath: ReferenceWritableKeyPath<Foo, String?> = \.name

let keyPaths: [AnyKeyPath] = [keyPath]
问题

现在我们有一个经过类型擦除的AnyKeyPath集合,其本质都是ReferenceWritableKeyPath(AnyKeyPath的rootType和valueType均已知)。

我希望将AnyKeyPath转换回ReferenceWritableKeyPath,并通过它执行类似foo.name = nil的赋值操作。目前实现的写法非常繁琐,以下是我当前的代码:

func tryWrite<Base>(_ newValue: Any?, to: inout Base, through: AnyKeyPath, withKnownKeyPathValueType kpValueType: Any) 
{
    return _tryWrite(newValue, to: &to, through: through, withKnownKeyPathValueType: kpValueType)
}

func _tryWrite<Base, Value>(_ newValue: Any?, to: inout Base, through: AnyKeyPath, withKnownKeyPathValueType: Value)
{
    guard let kp = through as? ReferenceWritableKeyPath<Base, Value> else {
        print("failed to cast keypath")
        return
    }
    to[keyPath: kp] = (newValue as! Value)
}


tryWrite(nil, to: &foo, through: keyPaths.first!, withKnownKeyPathValueType: Optional(""))

这段代码可以运行,但肯定有更优的实现方式。请问更好的方法是什么?

背景说明

这是一个简化示例。实际场景中,我正在实现一个类似SwiftData的数据框架中的“删除规则”,定义方式如下:

@Relationship(deleteRule: .nullify, inverse: \Bar.blah) var children: [Bar] = []

在删除操作期间,我需要合并所有需要置空引用的对象,因此得到了[AnyKeyPath]集合,需要将它们转换回ReferenceWritableKeyPath。

因此必须使用KeyPath来实现,不能采用其他替代方案(比如捕获闭包)。


优化方案

方法1:基于已知类型信息的直接转换

利用AnyKeyPath自带的rootType和valueType属性,结合泛型约束简化转换逻辑,无需额外传递类型占位符:

func setValue<Base>(_ newValue: Any?, for base: inout Base, through keyPath: AnyKeyPath) {
    // 验证根类型匹配
    guard keyPath.rootType == Base.self else {
        print("KeyPath根类型不匹配")
        return
    }
    // 尝试转换为可写KeyPath并赋值
    guard let writableKP = keyPath as? ReferenceWritableKeyPath<Base, Any>,
          let typedValue = newValue as? Any else {
        print("KeyPath转换或值类型转换失败")
        return
    }
    base[keyPath: writableKP] = typedValue
}

// 使用示例
setValue(nil, for: &foo, through: keyPaths.first!)

方法2:类型安全的扩展封装

给AnyKeyPath添加扩展,封装类型转换逻辑,提升代码可读性和复用性:

extension AnyKeyPath {
    func asReferenceWritable<Base, Value>() -> ReferenceWritableKeyPath<Base, Value>? {
        guard self.rootType == Base.self, self.valueType == Value.self else {
            return nil
        }
        return self as? ReferenceWritableKeyPath<Base, Value>
    }
}

// 使用示例
if let kp = keyPaths.first?.asReferenceWritable() as ReferenceWritableKeyPath<Foo, String?>? {
    foo[keyPath: kp] = nil
}

方法3:针对引用类型的简化写法

由于ReferenceWritableKeyPath仅适用于引用类型,可省略inout参数,直接通过对象实例修改属性:

func setValueForReferenceType(_ newValue: Any?, for base: AnyObject, through keyPath: AnyKeyPath) {
    guard let writableKP = keyPath as? ReferenceWritableKeyPath<AnyObject, Any>,
          let typedValue = newValue as? Any else {
        print("转换失败")
        return
    }
    base[keyPath: writableKP] = typedValue
}

// 使用示例
setValueForReferenceType(nil, for: foo, through: keyPaths.first!)

内容的提问来源于stack exchange,提问作者Bryan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 17:26:05