Generics still causing pain

First, I want to declare a protocol that will only apply to final classes that derive from BaseObject:

protocol KeyPathDeclaration where Self : BaseObject
{
  static func keyPaths() -> [PartialKeyPath<Self>]
}

This is then implemented in the final class:

final class Person : BaseObject, KeyPathDeclaration
{
  static func keyPaths() -> [PartialKeyPath<Person>]
  {
    return [\Person.name, \Person.age]
  }
  
  var name: String = ""
  
  var age: Int = 0
}

I then have a "property bag" which will be held in the BaseObject class and which will be populated by passing in the KeyPaths from the final class:

class AnyPropertyBag
{
  var ownerObject: AnyObject
  
  init(ownerObject: AnyObject)
  {
    self.ownerObject = ownerObject
  }
}

class PropertyBag<ownerT> : AnyPropertyBag where ownerT : KeyPathDeclaration
{
  var owner: ownerT
  {
    return ownerObject as! ownerT
  }
  
  required init(for owner: ownerT)
  {
    super.init(ownerObject: owner)
    
    let keyPaths: [PartialKeyPath<ownerT>] = ownerT.keyPaths()
    
    for _ in keyPaths
    {
      // population code
    }
    
    print(owner)
  }
}

What I would then like to do is have a "factory" method in the BaseObject class that would supply an instance of the PropertyBag to be held in the "properties" var:

class BaseObject
{
  static func createProperties<typeT>(for owner: typeT) -> AnyPropertyBag where typeT : KeyPathDeclaration
  {
    return PropertyBag<typeT>(for: owner)
  }
  
  lazy var properties: AnyPropertyBag = type(of: self).createProperties(for: self as! KeyPathDeclaration)
}

I have made the createProperties method generic to avoid the dreaded Self/AssociatedType problems involved with manipulating KeyPathDeclaration.

But, after several days of experimentation to get this right, I still end up with two errors on the properties declaration:

  1. Cannot invoke 'createProperties' with an argument list of type '(for: KeyPathDeclaration)'
  2. Expected an argument list of type '(for: typeT)'

I can do this kind of thing in C# (I have been developing in Obj-C and Swift from before Swift was launched) but, with Swift and its refusal to cope with generic protocols without all manner of convoluted workarounds, I'm still stumped.

Anyone got any ideas what I am missing here?

Have a factory that applies only to KeyPathDeclaration objects, otherwise store "blank" value:

protocol KeyPathDeclaration {
    static func keyPaths() -> [PartialKeyPath<Self>]
}

class AnyPropertyBag {
}

class PropertyBag<ownerT> : AnyPropertyBag where ownerT : KeyPathDeclaration {
    required init(for owner: ownerT) {
        for keyPath in ownerT.keyPaths() {
            print("- \(keyPath)")
        }
    }
}

protocol PropertiesFactory {
    func createProperties() -> AnyPropertyBag
}

extension PropertiesFactory where Self: KeyPathDeclaration {
  func createProperties() -> AnyPropertyBag {
      return PropertyBag(for: self)
  }  
}

class BaseObject
{
    lazy var properties: AnyPropertyBag =
      (self as? PropertiesFactory)?.createProperties()
      ?? AnyPropertyBag()
}


final class Person : BaseObject, KeyPathDeclaration, PropertiesFactory
{
    static func keyPaths() -> [PartialKeyPath<Person>] {
        return [\Person.name, \Person.age]
    }
    
    var name: String = ""
    var age: Int = 0
}

Also: shouldn't ownerObject be weak?

class AnyPropertyBag
{
  var ownerObject: AnyObject

Thank you so much Pavel. The missing piece was to declare the createProperties method in the PropertyBagFactory declaration as well as in the extension:

protocol PropertyBagFactory
{
  func createProperties() -> AnyPropertyBag
}

Unfortunately, as you showed, this meant adding PropertyBagFactory to the declaration of Person; something that I didn't want to require users of the framework to have to do.

Fortunately, I was able to "hide" that by deriving KeyPathDeclaration from PropertyBagFactory :wink:

And, yes, I was going to add the weak part when I got everything else working. The last thing I wanted was unexpected loss of a reference clouding things up.

So, now I'm a happy bunny once more :smiley:

Better keep them separate, and typealias composition:

typealias Some = KeyPathDeclaration & PropertiesFactory

final class Person : BaseObject, Some {

Hmmm. I know what you're getting at but, whereas the KeyPathDeclaration part has to be enforced at each final class, I really wanted BaseObject to do all the "heavy lifting" of creating the propertyBag behind the scenes.

Ideally, I would have liked to place a static factory method, that references Self, on the protocol, adopted by BaseObject, so that became the only point of reference to the factory.

Unfortunately, due to restrictions on Self-referencing protocols, which simply don't exist in other languages (like C#), this became the cause of several wasted days trying to find a way around those restrictions.

Come to think of it, deriving KeyPathDeclaration from PropertyBagFactory is not such a daft idea; I actually use an extension to KeyPath as a factory to create the individual, strictly typed, generic, Property objects that will reside in the PropertyBag.

Terms of Service

Privacy Policy

Cookie Policy