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

Swift UI introduces an Identifiable protocol as well as the related IdentifierValuePair and IdentifierValuePairs types and the identified(by:) method.

I believe that Identifiable is a "currency" protocol with relevance to an extremely broad range of Swift code and should therefore be moved into the standard library along with the related types and method. These will be useful to any generic code that works with values that represent a snapshot of the state of an entity.

Related thoughts

Related to this topic, there are a couple of symbols we may want to consider moving.

As mentioned in an earlier post, I also believe StaticMember should be in the standard library if unless we intend to replace it with a language solution.

BindableObject is appropriately placed because SwiftUI's notion of Binding is tightly tied to the domain of the framework. But a more general notion of ObservableObject with a didChange requirement would be very broadly useful. We may want to consider introducing this protocol at a lower level and having BindableObject refine it.

Finally, the notion of a Cancellable is broadly applicable to any code that deals with cancellable side effects. This means that Cancellable and AnyCancellable are also good candidates to be "currency" types. Combine feels like too specific a framework for these symbols.

What does the community think? Should we pull up some of these currency types?

43 Likes

I would be in favor of this. I have definitely written my own Identifiable protocol before, albeit under an existing name.

+1

2 Likes

Agreed. I have ended up defining my own Identifiable protocols.

2 Likes

I didn’t mention it in the opening post, but I have had my own Identifiable protocol for quite a while as well.

Agree. Such things should be in appropriate place

+1. I once created a protocol with the exact name and an id property. There are countless use cases.

It is in a way. One can also argue that observing another object for changes is a general and frequent intention, not limited to the UI layer.

"Model objects" that directly adopt BindableObject depend on SwiftUI, which, in my book, moves them out of the model into the UI layer, for all practical purposes of dependence. Domain- and business logic do not depend on SwiftUI, so I don't want to implement it that way.

So, I experimented with an intermediate layer in SwiftUI, basically a SwiftUI data source that lives in the SwiftUI layer and wraps my model object. This data source would be specific to the View and could even be a nested class.

But I'd love to get around this indirection, just using Swift (and maybe Combine) in my model. :thinking:

3 Likes

As far as they are useful without SwiftUI and without Combine, I'm all for it.

It also needs to be considered whether those symbols amount to something bigger than the sum of them, some consistent overarching concept/theme/metaphor that strings these primitives together, even without knowing anything about Combine / SwiftUI, if that makes sense ... Edit: can they be meaningfully combined outside their mothership frameworks, is what I mean.

In other words, you implemented a view model.

Because it depends on SwiftUI, it is not a view MODEL but more comparable to a good old UITableViewDataSource.

I'm also in favor of bringing Identifiable et al. to the standard library, and am also someone who's made and used a custom Identifiable protocol for a while now. I was actually pleasantly surprised to see how closely SwiftUI's matched my own.

Do the Identifiable protocol and the identified(by:)/IdentifierValuePairs helpers as implemented in SwiftUI meet the needs of your own uses? In what kinds of situations have you used your version of this protocol?

Yes! That is what motivates the pitch. Unsurprisingly the use cases usually involve (often generic) code that works with representations of (often part of) the state of an entity. Diffing and caching are common examples.

Let’s consider the diffing use case since it has been discussed elsewhere lately and is the motivating use case in SwiftUI. User interfaces often involve collection(s) of items, each of which represents an entity. In order to provide a high quality user experience when updating such a user interface with new content you need to be able to distinguish between the identity of the represented entity and the representation of (part of) the state of the entity that is presented to the user.

Identifiable and the related types enable diff algorithms that are able to make this distinction. Specifically, they support in-place updates of the UI when the state of an entity changes via algorithms that report changes in addition to insertions, deletions and moves. (Note: sometimes the state of an entity changes and the position of the entity moves relative to other elements in the collection)

In the context of this pitch it’s important to point out that just because the diff is applied at the user interface level does not mean the diff algorithm needs to run at the user interface level. It can be desirable to compute the diff in the model layer. For example, the model layer updates may be processed in the background and the diff can be computed before moving back to the main thread to apply the changes to the UI*. There may also be more than one simultaneous presentation of the same data in the UI, in which case computing the diff at the UI level is redundant.

In my experience, the model layer code that performs these computations often has no dependencies outside the standard library itself, or sometimes Foundation. There’s no way I want this code to take a dependency on SwiftUI. If Identifiable and friends don’t move to the standard library I will need to continue using my own variation of this protocol for no good reason other than it being declared in the wrong place. I suspect many of the other people who have mentioned in this thread using their own variation are in the same boat. Everyone will be better off if we establish a common currency for these concepts in the standard library.

footnote

*One pattern I have found useful is to have a stream of changes rather than a stream of values. Each change includes a before snapshot, an after snapshot and diff metadata.

7 Likes

What elements of standard library itself will conform to Identifiable? Perhaps just having nice things inside standard library without any application for the library itself is likely not the right way of development.

On top of my head I could imagine Optional and Never conform to that protocol, while Optional would conform conditionally to it if Wrapped conforms to it.

2 Likes

IdentifierValuePair would conform and would be the element type of IdentifierValuePairs which would be the result type of identified(by:). This would be the same as it is in SwiftUI today, except moved to the standard library where they would be accessible to all model-layer code in addition to view-layer code.

If only newly added IdentifierValuePair would conform to newly added Identifiable, and Optional for Identifiable wrapped case, this sounds like just to have some glue infrastructure to support new type, but not actually using it inside standard library.

One of the stated principles is to put "currency" types into the standard library. I don't believe any code in the standard library uses Result, yet the community decided the correct place for it is the standard library. The same rationale applies here.

12 Likes

So, I went spelunking in my hard drive and found a bit of code I wrote way back in the Swift 2 (!) days, before Evolution even started:

protocol Identifiable {
    typealias Identity: Equatable
    var identity: Identity { get }
}

Slightly different from the proposal, but the concepts are the same; hell, even the name is the same.

I think this is a fundamental concept. We can probably move it later without breaking ABI using a little compiler hackery, but whenever it happens, I think it might make a good addition to the standard library.

11 Likes

But why wait? Is there really not time to make the change now? It’s a straightforward change that shouldn’t require changes in SwiftUI’s implementation since the Swift module is implicitly imported.

3 Likes

I don’t think that it’s necessarily too late, but I am sure that it’s late.

2 Likes