Property Wrapper where wrapped type is a protocol, ignores the protocol properties

Hi everyone, to start I'm not entirely sure of my design, nevertheless here it is with my problem :
I'm currently developing an application with SpriteKit and the SpriteKit scene editor. To simplify the cumbersome var/optional writing of the elements of the scene I wrote this PropertyWrapper :

import SpriteKit
@propertyWrapper
@available(iOS 10.0, *)
public struct ChildNode<NodeType:SKNode>{
private var storage: NodeType?
private let childNodeName:String

public init(wrappedValue: @autoclosure @escaping () -> NodeType, withName name:String)
{
	self.childNodeName = name
	self.wrappedValue = wrappedValue()
}

public init(withName name:String)
{
	self.childNodeName = name
}

public var wrappedValue:NodeType
{
	get
	{
		guard storage != nil else {
			fatalError("ChildNode property is nil")
		}
		return storage!;
	}
	set
	{
		storage = newValue
	}
}

public mutating func setChild(from root:SKNode)
{
	
	if let rawNode = root.childNode(withName: childNodeName)
	{
		if let node = rawNode as? NodeType
		{
			storage = node
		}
		else
		{
			fatalError("ChildNode \"\(childNodeName)\" of type \(type(of: rawNode)) can not be converted to \(NodeType.self) ")
		}
	}
	else
	{
		fatalError("ChildNode \"\(childNodeName)\" can not be found ")
	}
}

}

I use it like that :

@ChildNode(withName: "obj")
private var myObj: MyClass = MyClass()

And it works fine until here.

Now I want "myObj" to be of type "MyProtocol" the code becomes :

@ChildNode(withName: "obj")
private var myObj: MyProtocol = MyClass()

Where "MyProtocol" is :

import SpriteKit
public protocol MyProtocol:SKNode
{
    var myProperty:String{get set}
}

Now when I try to access "myProperty" on "myObj" the compiler says :

Value of type 'SKNode' has no member 'myProperty'

How to get the compiler to understand that "myObj" is of type "MyProtocol" when it's already stated in the declaration?

I think you need to do something like:

import SpriteKit

public protocol MyProtocol: SKNode
{
    var myProperty : String { get set }
}

public class MyClass : MyProtocol {
{
    var myProperty : String = "Initial Value"
}

@childNode(withName: "obj")
private var myObj: MyClass = MyClass()

I think you need to create a concrete type like MyClass to use in a property wrapper. A protocol simply outlines a set of requirements, it does not actually cause a variable with an address to be generated. In your examples, the compiler is trying to generate a var in the object code, ie, create an allocation of a set of space in memory to hold myProperty. Since myProperty could be a stored property, a computed property, or something else, simply having the protocol requirements doesn't provide the information the compiler needs to actually generate code. So, it starts up the inheritance chain. SKNode is a concrete type that could have the relevant information for the compiler to generate the code. It couldn't find it. I suspect (not familiar with SpriteKit) SKNode is a type derived from NSObject, so the compiler stops there. If SKNode had a concrete super-class, it would keep going until if found the necessary code, or reached the ultimate base class. Thus, the error message.

If you want a hierarchy of classes, you could do something like this:

class BaseClass : MyProtocol {
{
    public var myProperty : String = "Initial Value"
}

public class MyClass1 : BaseClass {
   ... Other Stuff for MyClass1 ...
}

public class MyClass2: BaseClass {
   ... Other stuff for MyClass2 ...
}

@childNode(withName: "obj1")
private var myObj1 : BaseClass = MyClass1()

@childNode(withName: "obj2")
private var myObj2: BaseClass = MyClass2()

Depending on your needs, you may be able to dispense with the Protocol.

I'm not really convinced. From the proposition here , my code should be translated to :

@ChildNode(withName: "obj")
private var myObj: MyProtocol = MyClass()

// translats to : 

private var _myObj: ChildNode<MyProtocol> = ChildNode<MyProtocol>(withName: "obj", wrappedValue: MyClass())
var myObj: MyProtocol {
  get { return _myObj.wrappedValue }
  set { _myObj.wrappedValue = newValue }
}

And I don't know any restriction on using generic with a protocol like that.
For instance, with this file (used in my exemple above)

import SpriteKit
public protocol MyProtocol:SKNode
{
    var myProperty:String{get set}
}

public class MyClass:SKNode, MyProtocol
{
    public var myProperty: String = "my property!"
}

public class MyGenericClass<T>
{
    public var myObj:T
    init(myObj:T)
    {
    	self.myObj = myObj
    }
}

And this usage :

let t = MyGenericClass<MyProtocol>(myObj: MyClass())
print(t.myObj.myProperty)

The code compiles and works just fine and the property is printed.

Also, from my experience the whole point of having protocol is polymorphism and having a mandatory abstract implementation of it makes it a bit cumbersome.

Also, after further investigation, I realise that the problem occurs when we add a type requirement on the generic.
Like that :

 public class MyGenericClass<T:SKnode>
{
    public var myObj:T
    init(myObj:T)
    {
    	self.myObj = myObj
    }
}

Now, for what i understand, because MyProtocol is a protocole and SKNode is a class, the requirement on the protocole "public protocol MyProtocol:SKNode" doesn't actually says that MyProtocol is a SKNode hence the compilation error.

The question is : is it normal?

PS: I will close this post and open a new one regarding the generic and protocol observation.

All of your examples that work are concrete classes, even if they are parameterized by a generic. They are all defined ultimately as classes.

Protocols are necessary for polymorphisms with structs, enums, and classes. Structs and enums can't inherit like classes to preserve value semantics, but, they can conform to protocols. Polymorphism with classes can be accomplished without protocols, using an abstract base class, hence my comment that you could dispense with the protocol if you wanted to. However, if you want to be able to have all objects in Swift be able to be related polymorphically, then you need to use a protocol. For example, the Hashable protocol is conformed to by classes (SKNode), Int (struct), and various enum types in Cocoa.

A protocol cannot be instantiated as a "thing". It is only a description of type requirements.

I understand what you are saying and I agree with it. However I don't try to instantiate a protocol. I try to type a property with this protocole and to use the protocol in a generic.

As you might have noticed the only difference between my two exemples (the one that works and the one that doesn't) is that I added ":SKNode" to the generic type requirement. The usage of the code is absolutely identical.

The problem is that "MyProtocol:SKNode" is note recognised as inheritance for the generic "MyGenericClass<T:SKNode>"

Because MyClass is derived from a concrete class (SKNode), an actual property (variable) was generated in the code, and that property satisfies the requirements of MyProperty. MyClass also satisfies the requirement that it inherit from SKNode. When you created an instance with MyClass(), actual storage space was assigned and a myProperty came into existence. The declaration of MyGenericClass<MyProtocol>(obj: MyClass()) works because MyClass conforms to MyProtocol.

When you declare protocol MyProtocol : SKNode what you are saying is that any type that states it conforms to MyProtocol has to inherit from SKNode, because SKNode is a class, a concrete object type. But, MyProtocol is not itself a class or struct or enum that causes the compiler to generate code or allocate space and assign addresses. It only states requirements for those classes, structs, enums that conform to it. Only concrete classes that conform to (it's not inheritance from) MyProtocol can do so. Hence, the need for MyClass.

If SKNode was another protocol, the requirement would be that types conforming to MyProtocol also have to conform to SKNode.

This is the way it is supposed to work, based on my understanding of the language.

First, thank you, for you answers and to take the time for it. Note that I understand what you are saying and agree with most of it, I have a good understanding of what a protocol, a class, an enum and a struct are. But you still miss the point :

MyGenericClass<MyProtocol>(obj: MyClass())

Works fine when MyGenericClass is defined this way :

public class MyGenericClass<T>{...

But doesn't work when MyGenericClass is defined this way :

public class MyGenericClass<T:SKnode>{...

In every cases there is an actual obj being create and this object is an instance of MyClass.
In every cases T is MyProtocol. The problem is that the generic where type T is a protocol works but when the compiler sees T:SKNode where T is a protocol. the compiler should use the same definition as Protocol:SKNode (the class -of the actual object passed- that conform to this protocol T should extend SKNode) and not the same definition as Class:SKNode (The type T should extend SKNode).

I think the subtlety here is about what does protocol MyProtocol : SKNode mean and what does the constraint <T: SKNode> mean. The protocol statement says that any type that claims to conform to MyProtocol has to derive from SKNode (which makes the protocol class-only, but not a class). It also states that any type conforming to MyProtocol provide a property call myProperty which is a String and can be set and mutated. But, the important point for the following discussion, is that MyProtocol is not a class or a sub-class but an existential type that puts requirements on conforming types. It does not "extend" a type itself (which, if I understand your usage of the term, would still make MyProtocol a class), only puts requirements on any type that claims to conform to the protocol.

An extended version of SKNode would be something like:

public class MySKNode : SKNode, MyProtocol {
    public var myProperty : String = "Initial Value"
}

public class MyClass : MySKNode { }

That base class approach works like a charm in your use case if you use MyGenericClass<MySKNode>.

In your use of the constraint <T: SKNode>, you are constraining your class MyGenericClass to allow only classes that derive from SKNode. So, T has to be a class, and it has to be an SKNode or a class derived from SKNode. MyProtocol is not a class, and it does not "inherit" from SKNode, only levies requirements on types. So, it can't satisfy the constraint. Without any constraint ( <T>), any type qualifies and everything compiles.

This is all based on my reading the fine print in the Swift Language Reference and going through the forum threads. I have also read that this subtlety is a key difference between Swift protocols and Objective-C protocols, where the Objective-C protocol does "extend" a class.

Now, a different point may be, should your use case be supported? I can't answer that.

It also works if the constraint is class MyGenericClass<T: MySKNode>.

With regard to Objective-C protocols I made in my previous post, I mis-lead you. I was mixing up Objective-C protocols and categories, which correspond to Swift extensions. I was never much of an Objective-C programmer, just enough to create usable macOS application UI's, with most of the business logic in C++, C, and Fortran

Terms of Service

Privacy Policy

Cookie Policy