最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

swift - Convert AnyKeyPath to ReferenceWritableKeyPath with Optionals - Stack Overflow

programmeradmin1浏览0评论

Context

Consider this class and the KeyPath instances:

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

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

let keyPaths: [AnyKeyPath] = [keyPath]

Question

We now have a collection of type-erased AnyKeyPath that are really ReferenceWritableKeyPath under the hood. (The rootType and valueType of the AnyKeyPath are set.)

I want to convert the AnyKeyPath back to a ReferenceWritableKeyPath and use it to set foo.name = nil. But the ergonomics of that are downright laughable. Here's what I've got:

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(""))

This works, but there HAS to be a better way. What is that better way?

Background

This is a simplified example. In reality, I'm implementing "delete rules" in a data framework similar to SwiftData. You specify them the same way:

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

During a delete operation, I coalesce all the objects that need references nullified, which is how I end up with a collection of [AnyKeyPath] that I need to turn back into ReferenceWritableKeyPath.

Hence the need to make KeyPath work rather than some other alternative, such as capturing closures.

Context

Consider this class and the KeyPath instances:

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

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

let keyPaths: [AnyKeyPath] = [keyPath]

Question

We now have a collection of type-erased AnyKeyPath that are really ReferenceWritableKeyPath under the hood. (The rootType and valueType of the AnyKeyPath are set.)

I want to convert the AnyKeyPath back to a ReferenceWritableKeyPath and use it to set foo.name = nil. But the ergonomics of that are downright laughable. Here's what I've got:

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(""))

This works, but there HAS to be a better way. What is that better way?

Background

This is a simplified example. In reality, I'm implementing "delete rules" in a data framework similar to SwiftData. You specify them the same way:

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

During a delete operation, I coalesce all the objects that need references nullified, which is how I end up with a collection of [AnyKeyPath] that I need to turn back into ReferenceWritableKeyPath.

Hence the need to make KeyPath work rather than some other alternative, such as capturing closures.

Share Improve this question edited Feb 1 at 9:51 Joakim Danielson 52.1k5 gold badges33 silver badges71 bronze badges asked Feb 1 at 9:23 BryanBryan 5,7794 gold badges46 silver badges79 bronze badges 8
  • What's bad about this? What does "better" mean? – Sweeper Commented Feb 1 at 9:33
  • How is this different from your previous question? Does optionals break your answer here? I believe that answer will still work with optionals. – Sweeper Commented Feb 1 at 9:38
  • @Sweeper - yes. Because you can't pass nil as Value. You'd be building a ReferenceWritableKeyPath<Foo, nil>, which isn't a thing. – Bryan Commented Feb 1 at 9:40
  • Ah, you can just pass String?.none. See also stackoverflow/q/79094440/5133585 – Sweeper Commented Feb 1 at 9:42
  • @Sweeper - I'm frustrated because the type-erased AnyKeyPath has all the information it needs (rootType and valueType) but because you have to specialize the generics and can't just do someAnyKeyPath as! ReferenceWritableKeyPath<all-this-crap-is-known>, I end up needing to initialize an instance of the valueType. Here's that's trivial (an empty string). In my real case, that initialization may not be trivial. So "better" means something more ergonomic and efficient. – Bryan Commented Feb 1 at 9:45
 |  Show 3 more comments

1 Answer 1

Reset to default 1

Your own answer here works with optionals too.

It's just that you cannot directly pass nil to the with: parameter. You need to construct a nil from the value type of the key path. Just like how you opened the existential Any with an extra type parameter in _tryWrite, you can open an existential metatype in the same way.

// this is helper function we use to open the existential
func typedNil<T: ExpressibleByNilLiteral>(of type: T.Type) -> T {
    return nil
}

// this is same as your answer in the linked question
func _tryWrite<Base, Value>(to: inout Base, through: AnyKeyPath, with: Value) {
    guard let kp = through as? ReferenceWritableKeyPath<Base, Value> else {
        print("failed to cast keypath")
        return
    }
    to[keyPath: kp] = with
}

func tryWriteNil<Base>(to: inout Base, through: AnyKeyPath) {
    guard let valueType = type(of: through).valueType as? any ExpressibleByNilLiteral.Type else {
        print("Key path value type is not optional!")
        return
    }
    return _tryWrite(to: &to, through: through, with: typedNil(of: valueType))
}
发布评论

评论列表(0)

  1. 暂无评论