Move SwiftUI's Identifiable protocol and related types into the standard library

I thought that @anandabits's argument was pretty strong. Being able to mix and match "cancellables" from different libraries makes sense. One way to make it even more generic and useful would be to rename it to Disposable (with the accompanying dispose() method). Then it would be crystal clear that it can also be used for any kind of resource management. Once we have that, we may want to even steal a great idea from C#:

using let resource1 = createResource(), let resource2 = createResource() {
    resource1.foo()
    resource2.bar()
}

would be equivalent to:

let resource1 = createResource()
let resource2 = createResource()
resource1.foo()
resource2.bar()
resource2.dispose()
resource1.dispose()
5 Likes

While we're talking about moving symbols around, I just noticed that SwiftUI includes Optional: Publisher where Wrapped: Publisher. If this conformance is going to exist it should really live in Combine. Putting this conformance in SwiftUI breaks the rule that a module should not conform a type it doesn't declare to a protocol it doesn't declare. I can't think of any good reason to allow SwiftUI to violate this rule in this case.

18 Likes

The rule is usually stated as “own” instead of “declare”, which seems fine here. It might complicate the story if SwiftUI and/or Combine are made open-source in future, though.

1 Like

That is how the rule is usually stated. I rephrased it for clarity. The intention behind the rule is to avoid a scenario where more than one conformance can exist. When conformance is declared in the module declaring either the type or the protocol multiple conformances are impossible. The compiler will not allow it.

The fact that Apple “owns” both Combine and SwiftUI is not an excuse for declaring this conformance in a module that declares neither the protocol nor the type. It opens the door to people declaring this conformance themselves in a module that does not depend on SwiftUI. If that happens it will lead to trouble as soon as that module is imported by a module that also imports SwiftUI. This is a pretty obscure corner of SwiftUI. It’s quite possible this could happen by accident.

There shouldn’t be any harm caused in moving this conformance declaration to Combine if it needs to exist. There is obviously no way to propose that through the SE process but hopefully if we file radars we can convince Apple to do the right thing here. I think this is important both for this specific case, but also to avoid setting a dangerous precedent in Apple’s own frameworks.

4 Likes

Sure, but someone declaring that conformance themselves would be violating the rule, as they own neither. I have nothing against your suggestion to move the conformance, and it is probably justified on other merits, but it's not really a violation of the rule. Apple can easily ensure that they never introduce the conformance in Combine, avoiding any multiple conformance issues.

1 Like

As far as I know, it is a violation of the rule as it is traditionally interpreted in other programming language communities with similar concerns. You’re arguing that Swift should adopt a looser interpretation of the rule. I don’t think that’s a good idea.

1 Like

Sure they can make sure that they don't have multiple conformances in code at Apple, but can they prevent someone else from providing the conformance themselves? The answer is no. Anyone could write a module for Darwin that imports Combine and adds their own conformance to Publisher on Optional. Then someone else could later import this module into a view in their iOS app and now they have a conformance collision because there is ambiguity between the Optional conformance declared in the 3rd party module module and SwiftUI.

Just because they aren't open source doesn't mean they can't be imported and used in 3rd party modules. It just restricts their usage to Apple OSes.

This would also be the case if the conformance was currently in neither module and was later added to Combine. The person would be violating the rule because they don't own the type or the protocol, which is why the rule exists. But I can happily do this across frameworks that I own, because I control their evolution and dependency relationships, and so can Apple. But, like I said, I'm not opposed to moving it, I just don't believe it's justified by that rule.

2 Likes

Thinking out loud: if the rule is defined in term of modules, then it could be enforced by the compiler.

It certainly could be, but I think that would be overly restrictive. When you own the final build target it is possible (and sometimes useful) to safely violate this rule. This can be done safely because you have global knowledge of all conformances available in a program so you are able to guarantee there is only one conformance of a given type to a given protocol. SwiftUI has no way to make such a guarantee. That is the problem with it providing a Optional: Publisher where Wrapped: Publisher conformance.

1 Like

+1 those all look like great potential additions!

I think this makes a lot of sense.

I have been working with Kyle Macomber from the SwiftUI team on a more fleshed out proposal. We have decided to strip it down to a bare minimum. The entire implementation now fits below:

/// A class of types whose instances hold the value of an entity with stable identity.
protocol Identifiable {

    /// A type representing the stable identity of the entity associated with `self`.
    associatedtype ID: Hashable

    /// The stable identity of the entity associated with `self`.
    var id: ID { get }
}

extension Identifiable where Self: AnyObject {
    var id: ObjectIdentifier {
        return ObjectIdentifier(self)
    }
}

Please see the new draft for the details.

15 Likes

One question about your extension. Will the where clauses be ignored again or does it only happen with the same type constraint?

Reference thread: What kind of magic behavior is this?


Edit:

// error: Type 'S' does not conform to protocol 'Identifiable'
// FIXIT: Do you want to add protocol stubs?
struct S: Identifiable {}

// After FIXIT:
struct S: Identifiable {
  var id: ObjectIdentifier
}

So the inference rules yet again mess with the associated type here. We could avoid this wrong fixit if we'd add another extension:

extension Identifiable where ID == Never {
  var id: ID { fatalError() }
}

So if a client wanted to distinguish same-identity-but-changed-value, they would have to constrain the type to be Equatable as well as Identifiable? Is there a reason Identifiable does not itself refine Equatable?

Could you summarize these discussions? In particular, I’m interested in the removal of identifiedValue, which would seem to make it impossible to implement Collection.identified(by:) (which seems generally useful) in terms of this protocol. Will SwiftUI have an additional abstraction over Identifiable?

This would be a welcomed addition to the standard library.

We’ve used our own version of Identifiable for years across internal frameworks that have nothing to with UI and in customer facing applications to automatically assign identifiers to views such as UITableViewCell, Segues, ViewControllers, etc.

Thanks @anandabits for driving this addition…

Cheers!

Thanks Matthew for raising this. Based on the positive reaction from the community for sinking this down to be a general feature, the core team has decided it makes sense to schedule a review for this asap to it can still make it into the 5.1 ABI. I'll kick that review off shortly.

18 Likes

Because all identifiable objects are not Equatable. If you refine Equatable, you add a strong requirement on all Identifiable objects, which greatly limit the scope of this protocol, and no longer make it generic enough to be part of the standard library IMHO.

4 Likes

I’m chiming in that I also have an Identifiable protocol in my current codebase and the one in SwiftUI happens to be a superset of mine, so I’d be very happy to see that moved to the standard library.

I also have a UUIDIdentifiable protocol which (besides being poorly named) has the advantage of always using UUIDs as identifiers so it doesn’t have the limitations of having an associated type. I find both these protocols really useful.

Terms of Service

Privacy Policy

Cookie Policy