Is there possible to support inheriting from a generic type: class classA<T: classB>: T?

I just had a more specific use case for this pattern.

class G<T: AnyObject>: T {
  var property = default_value
}

As far as my understanding of this subject goes, it should be safe to use this pattern (if it was allowed) as long as the generic subclass provides default values for all stored properties it introduces. This way there no need to call super.init or am I wrong?

Furthermore I think there shouldn't be any issues with dynamic casts. You can easily upcast from G<T> to T. And you should be able to down-cast using as? just as we do it today.

My use-case was that I would like to introduce stored properties on classes that represent an object hierarchy without the need to sub-class every possible class to provide such functionality to all of them (an alternative to this pattern would be 'stored properties within protocol extensions').

cc @Slava_Pestov @John_McCall just to clarify, would such a use-case justify the introduction of this pattern OR the mentioned alternative solution? (I don't mean it in a way that it should happen immediately.)

I think an associated-objects-like "stored properties in extensions" feature would be significantly more interesting and feasible than doing a whole mess of design and implementation work around allowing generic subclassing.

11 Likes

Any chance there is already an internal compiler/runtime feature for pure Swift code? Combine provides magically a default implementation of objectWillChange stored property for conforming classes.

I have no idea how that works; they may in fact be using associated objects.

A bunch of us are curious about whether that is the case or whether it is something else. @Tony_Parker or @Philippe_Hausler are either of you able to say what technique is being used here?

Wouldn‘t that require the class to be a subclass of NSObject, their implementation seems to work on pure swift classes?! :thinking:


If there is no special compiler/runtime support from swift‘s side, then I‘m super curious what technique was used there.

The implementation of objectWillChange is a bit sneaky; there is no associated object. Instead we take advantage of knowing the metadata layout and key paths and access the existing storage from our wrapper. On the surface it seems like an associated object but in reality it is more like a metadata lens.

2 Likes

So the property is not injected into the conforming class, but how do you clean up when the model gets deallocated? I don‘t know a technique that would allow me to catch that.

so the wrapper in that case ends up being an ivar; that structure contains a reference that cleans everything up. Thankfully we don't really need any invalidation since the stream invalidation happens on cancel/terminal states. That being said there is a brief window of opportunity (with multithreaded streams) that someone could hold onto the stream and consequently cause an item to outlive it's normal lifespan I guess.

1 Like

I see. For what it's worth, this approach leaves a bit of a footgun:

class Test: ObservableObject {
   func publishChange() {
       objectWillChange.send()
   }
}

struct ContentView: View {
   @ObservedObject var test = Test()
   var body: some View {
       Button(action: { self.test.publishChange() }) {
           Text("Test")
       }.onReceive(test.objectWillChange) {
           print("received") // never gets printed when you tap the button
       }
   }
}

The above code compiles, but the publisher never sends as far as I can tell. What happens in this case?

When a Published property is added everything works as expected:

class Test: ObservableObject {
   @Published storage = true
   func publishChange() {
       objectWillChange.send()
   }
}
1 Like

Aaand I still have no clue how to replicate that technique. :sweat_smile::face_with_raised_eyebrow: How is there an ivar for pure protocol conformance?

@Helge_Hess1 gessed on Slack that you sneak the storage into a Published instance, that would be really sneaking and nonintuitive at all as the protocol does not require that.

Imagine something like this:

@propertyWrapper
struct Published {
    private publisher: ObjectWillChangePublisher
    // etc
}

That perhaps represents a bug, if you could file a feedback for us. I think it may be fixable.

Will do. Out of curiosity, where would the publisher be stored if there are no @Published wrappers on the class that conforms to ObservableObject?

1 Like

:point_up: Exactly my question/issue.

Filed. FB6976699

Unfortunately I cant really go to far in depth with that at the current moment, but suffice it to say we use memory associated with the object itself (this is part the reason why we require ObservableObject to be a reference type).

I will give a bit of interesting reading that is not completely tangential:

1 Like

Is this even safe for retroactive conformances? If I do extension UIView: ObservedObject {} will it light up in flames? :eyes:

1 Like

Did you consider doing this with a @propertyWrapper? I think it could become elegant enough ;)

I apologize in advance if this shouldn't be posted on an old topic. But here is a simplified example of a use case that I ran in to. Let's say this were possible:

class NonFirstResponder<T: NSFirstResponder>: T {
    var acceptsFirstResponder: Bool { false }
}

With that, any of the AppKit controls can have focusability disabled just by wrapping their type in NonFirstResponder<> when creating:

// A button with stock functionality:
button = NSButton(title: "Something", target: coord, action: #selector(coord.buttonClicked))

// An identical button that doesn't accept focus:
button = NonFirstResponder<NSButton>(title: "Something", target: coord, action: #selector(coord.buttonClicked))

I know the need to disable focusability isn't something that comes up very often. It's just a concise example. There's plenty of other functionality in AppKit besides acceptsFirstResponder that also requires sub-classing to use.

This isn't to dismiss earlier comments that this would be difficult to implement in the compiler. Just providing an example.

... Or is there already some way to accomplish this kind of thing without manually subclassing every type of control you need to use it on?