New workaround for "Cannot assign to immutable expression of type" error when setting via key path

I've come up with a, possibly simpler, solution to setting values by key paths on unknown types in a project of my own.

The requirements for the unknown type were :

public weak var subject: (AnyObject & KeyPathDeclaration)!

… where KeyPathDeclaration is :

public protocol KeyPathDeclaration
{
  static var keyPaths: [String : AnyKeyPath] { get }
}

extension KeyPathDeclaration
{
  public static func keyPathForProperty(name: String) -> AnyKeyPath?
  {
    return keyPaths[name]
  }
}

This is then implemented in a type like this :

struct Person
{
  var name: String
  
  var age: Int
}

extension Person : KeyPathDeclaration
{
  public static var keyPaths: [String : AnyKeyPath]
  {
    return ["name" : \Person.name, "age" : \Person.age]
  }
}

The problem arises when you need to use one of the key paths to set a value where the exact type of the target object is only known as :

  public weak var subject: (AnyObject & KeyPathDeclaration)!

… where trying to use subject[keyPath:_] to set a value provokes an error

"Cannot assign to immutable expression of type 'Any?'"

So, I played around for a bit and started thinking about some of the ideas in this thread. Here's what I came up with.

First, I create a new protocol :

public protocol KeyPathValueSetter
{
  func set<valueT>(_ value: valueT?, for keyPath: AnyKeyPath)
}

extension KeyPathValueSetter
{
  public func set<valueT>(_ value: valueT, for keyPath: AnyKeyPath)
  {
    if let keyPath = keyPath as? ReferenceWritableKeyPath<Self, valueT>
    {
      self[keyPath: keyPath] = value
    }
  }
}

Then I add in this protocol to the subject's requirements :

  public weak var subject: (AnyObject & KeyPathValueSetter & KeyPathDeclaration)!

Now I can set the value on the subject like this :

      let keyPath = type(of: subject).keyPathForProperty(name: propertyName)

      subject.set(value, for: keyPath)

Not quite a subscript but a lot more concise than some efforts I made to solve this :wink::sunglasses:

Why is the first parameter optional?

To allow the possibility of optional properties? Or it could just be a typo :roll_eyes::wink:

Terms of Service

Privacy Policy

Cookie Policy