Swift / Apple Development Chat

Andropov

Site Champ
Posts
615
Reaction score
773
Location
Spain
For the first time ever, I'm going to skip the State of the Union until tomorrow... it comes out at 1AM here, and I started a new job today. Too much excitement for a single day šŸ˜‚

SwiftData could be SUPER interesting, I'm really looking forward to see what they've done and how they've come around some of the (natural) problems with dealing with databases. Paginated scrolling could also be another longawaited improvement (although obviously way smaller scope), it was unreasonably difficult to make a good-behaving paginated list in SwiftUI (at least, without manually loading new pages). Curious about TipKit too.
 

ArgoDuck

Power User
Site Donor
Posts
105
Reaction score
167
Location
New Zealand
Main Camera
Canon
I havenā€™t watched the full state of the union yet even though its timing here was reasonable but Iā€˜m very excited about SwiftData and Swift macros, and i think the simplifications of SwiftUIā€˜s data flow is very welcome too. Looking forward to this weekā€™s sessions
 

Nycturne

Elite Member
Posts
1,136
Reaction score
1,483
SwiftData could be SUPER interesting, I'm really looking forward to see what they've done and how they've come around some of the (natural) problems with dealing with databases.

Digging in with what is kicking around, Iā€™m wondering if it might be a wrapper around CoreData. Something similar to that library that bridged CoreData to Swift structs. Thereā€™s a number of similarities.

Per Apple: ā€œItā€™s built on top of CoreDataā€™s persistence layerā€. So yeah, itā€™s CoreData with a more Swift-friendly approach. Annotate your model types, call it a day. Good.

EDIT: Looks like thereā€™s some concurrency support built into the new API using actors, but I canā€™t quite make out how it is supposed to work just from the docs. Still, being able to use run-of-the-mill Swift classes and just annotating how they should be stored in CoreData is pretty huge. If @Model objects are thread safe unlike managed objects, thatā€™s even bigger.
 
Last edited:

Nycturne

Elite Member
Posts
1,136
Reaction score
1,483
I havenā€™t watched the full state of the union yet even though its timing here was reasonable but Iā€˜m very excited about SwiftData and Swift macros, and i think the simplifications of SwiftUIā€˜s data flow is very welcome too. Looking forward to this weekā€™s sessions

@Observable seems to fix the over-invalidation problem with ObservableObject, so thatā€™s a pretty big deal.
 

Nycturne

Elite Member
Posts
1,136
Reaction score
1,483
Played a little bit with SwiftData. I like the approach, but found a couple bugs that I need to report it looks like:

- You can use structs inside your model classes. This makes composition possible. However it looks like using optionals in structs breaks things at the moment when it goes to read the values later.
- Enums don't work yet. Seems to generate some weird Core Data schema that doesn't actually work.

That said composition using structs does seem to help avoid generating relationships while still giving you composability as different fields in the struct become fields in the underlying CoreData managed object. Very nice.
 

Cmaier

Site Master
Staff Member
Site Donor
Posts
5,295
Reaction score
8,454
Played a little bit with SwiftData. I like the approach, but found a couple bugs that I need to report it looks like:

- You can use structs inside your model classes. This makes composition possible. However it looks like using optionals in structs breaks things at the moment when it goes to read the values later.
- Enums don't work yet. Seems to generate some weird Core Data schema that doesn't actually work.

That said composition using structs does seem to help avoid generating relationships while still giving you composability as different fields in the struct become fields in the underlying CoreData managed object. Very nice.
I have a massive app that bridges to objective C to handle SQLite, and if i had any energy Iā€™d finally do something about that. But I donā€™t.
 

Andropov

Site Champ
Posts
615
Reaction score
773
Location
Spain
Hah. Finally got around to watching (a bit of) the Platforms State of the Union. It's quite a flex that they managed to do things even simpler *while* adding more advanced features and (possibly) better performance. Observable macro should lead to less unnecessary view invalidations, as it works per-property instead of invalidating all the views that referenced the container object, and I see no obvious downsides (though I must admit I don't quite understand how macros work yet). This pretty much solves the issue where big observed models triggered a redraw of the entire UI, even when only one property that didn't affect most views had changed. This is a problem I ran into with SwiftUI in the past, so it's not just some academic benefit, this should have measurable impact on real apps.

Kudos to them. SwiftData does look like the modern approach to persistency they are promising, even if there's some bugs to iron out at the moment. Supporting structs directly was a must, since Apple strongly encourages bundling properties and small models into structs.
 

Nycturne

Elite Member
Posts
1,136
Reaction score
1,483
Hah. Finally got around to watching (a bit of) the Platforms State of the Union. It's quite a flex that they managed to do things even simpler *while* adding more advanced features and (possibly) better performance. Observable macro should lead to less unnecessary view invalidations, as it works per-property instead of invalidating all the views that referenced the container object, and I see no obvious downsides (though I must admit I don't quite understand how macros work yet). This pretty much solves the issue where big observed models triggered a redraw of the entire UI, even when only one property that didn't affect most views had changed. This is a problem I ran into with SwiftUI in the past, so it's not just some academic benefit, this should have measurable impact on real apps.

It looks like Macros are compile-time plugins for Swift. So someone writing a plugin gets a lot more control over the emitted code and visibility into the structure of things, and the end result is that you can add say, a notification callout when a property is edited complete with the name/identity of the property that changed. On the observing side I suspect the macros setup observation for you and take note of the properties accessed at compile-time to generate a bit of glue for you. In Xcode it looks like if you are debugging through the emitted code, the editor will "unfold" the emitted code and let you see it, but it's hidden otherwise.

This does seem to be Apple trying to push for more of a View-Model architecture when using this though. @Observable can be used to create a ViewModel, but it seems like you don't quite get the nice bindings via Combine with @Observable as you do ObservedObject, so you'll need to consider how to handle the Cancellables coming from assign(). Just something to be aware of.

That said, SwiftData should simplify things enough that half of the things I use a ViewModel for no longer apply. The main issue is more that I have a handful of micro-services that get used to handle caching of images/media, sync, and a handful of other tasks. So if you want to compose those things, it's either putting code in your view, or building a ViewModel to do composition. It really looks like Apple wants you to do the former using something like environment objects for injection. Which can work, but can lead to needing to think carefully about composition.

Kudos to them. SwiftData does look like the modern approach to persistency they are promising, even if there's some bugs to iron out at the moment. Supporting structs directly was a must, since Apple strongly encourages bundling properties and small models into structs.

To be clear, @Model objects need to be classes (it is still CoreData under the covers and @Model infers @Observable), but you can compose struct types into a model object. And those struct fields can still be used as search keys and the like it looks like, as the CoreData entity will get flattened.

Good example is that I have the concept of a "Title" that has multiple forms. The display title, the sort title, and the Siri title. This concept is repeated across multiple Core Data objects in my current project, and is a bit of a pain to manage, since anytime I update the display title, the Siri title should be regenerated. I can throw this into a struct now, unit test it in isolation, and compose it onto the various model objects that use the title this way.
 

Andropov

Site Champ
Posts
615
Reaction score
773
Location
Spain
This does seem to be Apple trying to push for more of a View-Model architecture when using this though. @Observable can be used to create a ViewModel, but it seems like you don't quite get the nice bindings via Combine with @Observable as you do ObservedObject, so you'll need to consider how to handle the Cancellables coming from assign(). Just something to be aware of.
I think they have been pushing in that direction for a while, their code samples have been using View-Model for a while. Personally, I think some people were abusing the MVVM pattern in places where it was not beneficial to decouple a view from its model.

That said, SwiftData should simplify things enough that half of the things I use a ViewModel for no longer apply. The main issue is more that I have a handful of micro-services that get used to handle caching of images/media, sync, and a handful of other tasks. So if you want to compose those things, it's either putting code in your view, or building a ViewModel to do composition. It really looks like Apple wants you to do the former using something like environment objects for injection. Which can work, but can lead to needing to think carefully about composition.
The way I've handled the image caching in the apps I've worked with (the specific use case was loading and caching images from URLs) was by creating a CachedImageView as a wrapper around SwiftUI's Image (mimicking the AsyncImage API, basically) and then creating a shared ImageCacher actor that handled the caching itself, with no ViewModel in the middle.

I did have to put a couple lines of code on the View itself (.task { self.loadedImage = try ImageCacher.loadImage(from: imageURL) } + some error handling), but the important stuff happens on the (shared) ImageCacher actor itself. And you naturally get some nice things from setting things up this way: the .task { ... } automatically cancels if the view is removed from the UI.

The ImageCacher is an actor to ensure that, in the case that several views request the same image (the same URL) at the same time, only one URL request is made and then the result is propagated to all callers. I borrowed the idea from a WWDC '22 talk [Protect mutable state with Swift actors], and then put some caching logic on top of it. I didn't even need an environmentObject to pass the reference to the ImageCacher instance, since it was reasonable to use a singleton there as we only wanted a single image caching system in the app, to avoid cache duplication. For cases where it makes sense to have separate systems (for example: data that is window-dependent in a multi-window app), yes, it looks like Apple favors environmentObject. I don't like those very much, as the compiler doesn't enforce their presence and it's IMHO to easy to forget one of them and cause a crash.

So basically I follow the architecture proposed in Swift concurrency: Update a sample app + a few ViewModels here and there for Views that manage entire screens (or several different UI elements), which usually do have view-specific logic that is also non-trivial. For self-contained UI components, I rarely add a ViewModel.
 

Nycturne

Elite Member
Posts
1,136
Reaction score
1,483
I think they have been pushing in that direction for a while, their code samples have been using View-Model for a while. Personally, I think some people were abusing the MVVM pattern in places where it was not beneficial to decouple a view from its model.

It's also probably an artifact of building monolithic views rather than decomposing them. Something I've done in a couple places that is biting me now.

The way I've handled the image caching in the apps I've worked with (the specific use case was loading and caching images from URLs) was by creating a CachedImageView as a wrapper around SwiftUI's Image (mimicking the AsyncImage API, basically) and then creating a shared ImageCacher actor that handled the caching itself, with no ViewModel in the middle.

Which is fine for images, and I do something similar. Things get a bit more complicated when you are managing downloaded media that the user can themselves interact with, and need to effectively manage a singleton from half a dozen different locations in the app.
 

Andropov

Site Champ
Posts
615
Reaction score
773
Location
Spain
It's also probably an artifact of building monolithic views rather than decomposing them. Something I've done in a couple places that is biting me now.
Yup, me too. I went to a SwiftUI lab on WWDC '20 because of a weird SwiftUI behavior, and the engineer pointed out (after the bug was solved) that the view I had onscreen was probably a candidate to be split into smaller views. And I think there was only about two screens worth of scroll (for the view body). I've since tried to keep that as a rule of thumb when building views.

Which is fine for images, and I do something similar. Things get a bit more complicated when you are managing downloaded media that the user can themselves interact with, and need to effectively manage a singleton from half a dozen different locations in the app.
Yeah, image caching is a particularly easy case.


I've just started watching the Swift macros talk. I am kinda worried about it because I'm not a big fan of code autogeneration. I've seen people implement horrendous ideas with that (no further than last week). There's a talk about Grand Central Dispatch (no idea which one) in which the engineer emphasized that the API should try to avoid making expensive operations too short or too simple to write, to prevent developers that use the API from falling into bad patterns unknowingly. I worry that macros could (potentially) be a tool to just override all that. Hopefully they've thought of that too.
 
Last edited:

Nycturne

Elite Member
Posts
1,136
Reaction score
1,483
Yup, me too. I went to a SwiftUI lab on WWDC '20 because of a weird SwiftUI behavior, and the engineer pointed out (after the bug was solved) that the view I had onscreen was probably a candidate to be split into smaller views. And I think there was only about two screens worth of scroll (for the view body). I've since tried to keep that as a rule of thumb when building views.

An example for me is a scrollable view with sorting and view modes. Items have context menus, etc, etc.

I made a generic view that did so much of the heavy lifting here. But the end result is that it winds up being this giant monolithic view that really needs something like a view model to hold state, push appropriate state back out to UserDefaults and so on. Precisely because it is generic. But it means this monolithic view handles displaying disparate collections in a similar way.

I've been playing with composed versions of this approach on the Mac side, and it does greatly simplify certain things. With SwiftData, I'll probably make another attempt at going full View-Model in my approach. Because so much of my glue code is based on trying to keep CoreData from vomiting all over everything and creating anti-patterns everywhere.

I've just started watching the Swift macros talk. I am kinda worried about it because I'm not a big fan of code autogeneration. I've seen people implement horrendous ideas with that (no further than last week). There's a talk about Grand Central Dispatch (no idea which one) in which the engineer emphasized that the API should try to avoid making expensive operations too short or too simple to write, to prevent developers that use the API from falling into bad patterns unknowingly. I worry that macros could (potentially) be a tool to just override all that. Hopefully they've thought of that too.

The optimistic side of me says that because these are compiler plugins, there's a bit of a hurdle to making new macros. There has to be a clear benefit to it over property wrappers or the built in stuff to be worth the investment.

This isn't your C/C++ preprocessor, thankfully.
 

Nycturne

Elite Member
Posts
1,136
Reaction score
1,483
I have to admit when said ā€œadding macros to Swift!ā€ I was a little concerned and confused, like thatā€™s not something that should be added in this day and age.

There's been discussion about macros for Swift for years now. There are just certain things that preprocessor macros are good at that are hard to replace when you get into a certain regime, but the Swift team also didn't want a preprocessor because that causes its own set of issues. So macros enabled by plugins are the compromise.

I have no real plan to write macros myself, but they will enable some interesting things when it comes to what Apple can do with the platform. @Observable and @Model being examples (#Predicate being another for generating predicates for SwiftData from Swift expressions at compile time with type checking). Specifically because the plugins for Apple's macros will be built into the SDK and so people can just use them.
 

Andropov

Site Champ
Posts
615
Reaction score
773
Location
Spain
The optimistic side of me says that because these are compiler plugins, there's a bit of a hurdle to making new macros. There has to be a clear benefit to it over property wrappers or the built in stuff to be worth the investment.

This isn't your C/C++ preprocessor, thankfully.
Yeah, to be fair you can write "enhancements" to an API that lead to antipatterns without macros too. For example, I've seen this very innocuous extension in a lot of projects:
Swift:
extension String? {
    var orEmpty: String = {
        return self ?? ""
    }
}
And while it can be quite handy sometimes, Swift is explicitly verbose with the ?? syntax for a reason. What I found in a lot of those codebases was that by making nil coalescing less obvious, good optional handling behavior was bypassed more often in cases that would have benefited from it (and now had to rely in checking for nil strings).

Anyway, after watching the talk I'm not worried anymore. Swift macros seem difficult enough to write that people who don't know what they're doing won't touch them.

I have to admit when said ā€œadding macros to Swift!ā€ I was a little concerned and confused, like thatā€™s not something that should be added in this day and age.
Thankfully, the first thing they say on the talk is: don't worry, it's nothing like other languages' macros, which is reassuring šŸ˜. I don't know if it's true, it seems to me that it is, but I don't know many other languages nearly as well as Swift.
 

Nycturne

Elite Member
Posts
1,136
Reaction score
1,483
Yeah, to be fair you can write "enhancements" to an API that lead to antipatterns without macros too.

Don't disagree, but at the very least I can "Go to definition" on "orEmpty" and see what the fuss is about and go "that's dumb" in the code review. Trying to figure out what MYGREATMACRO(someJunk) does and why I get a compiler error telling me that variable 'foo' doesn't exist is something else entirely.

Anyway, after watching the talk I'm not worried anymore. Swift macros seem difficult enough to write that people who don't know what they're doing won't touch them.

Yeah, this form of macro really does seem more like for library/framework folks than for people to create a whole new category of anti-pattern with. Honestly, making the macro operate after syntax has been parsed makes things easier for the compiler and avoids some of the worst things in C macros.

Thankfully, the first thing they say on the talk is: don't worry, it's nothing like other languages' macros, which is reassuring šŸ˜. I don't know if it's true, it seems to me that it is, but I don't know many other languages nearly as well as Swift.

C macros are the devil incarnate. By extension many Obj-C macros are the devil incarnate. Makes React Native Obj-C modules harder to read and I keep having to remind folks that this is not how Obj-C code is normally at work.
 

Andropov

Site Champ
Posts
615
Reaction score
773
Location
Spain
Don't disagree, but at the very least I can "Go to definition" on "orEmpty" and see what the fuss is about and go "that's dumb" in the code review. Trying to figure out what MYGREATMACRO(someJunk) does and why I get a compiler error telling me that variable 'foo' doesn't exist is something else entirely.
Hey. I wish I could say ā€œthatā€™s dumbā€ in code reviews :p

I saw a bit more of SwiftData and I think itā€™s definitely going to make Realm fall out of fashion. I canā€™t think of a reason to continue using Realm now, other than support for existing projects. I wasnā€™t a big fan of it anyway, I try to use the first party solutions whenever possible (plus Realm adds a LOT of compile time to a project).
 

Andropov

Site Champ
Posts
615
Reaction score
773
Location
Spain
Ooh the new SwiftUI inspector is great. I had my own hand-rolled version in one of my apps, almost exactly what they showed on the WWDC talk. I canā€™t wait to remove all that code for a first party inspector with iOS 17. And itā€™s a *resizable* sidebar on macOS, something I was honestly too lazy to implement. Yay!
 

Nycturne

Elite Member
Posts
1,136
Reaction score
1,483
Hey. I wish I could say ā€œthatā€™s dumbā€ in code reviews :p

I generally take more tact with my team members, but that's what I'm thinking when I type out "this seems overengineered", "why are we doing it this way? X makes more sense", etc.

I saw a bit more of SwiftData and I think itā€™s definitely going to make Realm fall out of fashion. I canā€™t think of a reason to continue using Realm now, other than support for existing projects. I wasnā€™t a big fan of it anyway, I try to use the first party solutions whenever possible (plus Realm adds a LOT of compile time to a project).

Yup, honestly the fact that I just created a class with composition and it mostly figured everything out to represent it in CoreData properly is yuge. Predicate macros help a ton as well since you describe predicates in terms of your model types. Just need to convince them to fix the bugs.

This is what I wanted 2 years ago to be honest.

Ooh the new SwiftUI inspector is great. I had my own hand-rolled version in one of my apps, almost exactly what they showed on the WWDC talk. I canā€™t wait to remove all that code for a first party inspector with iOS 17. And itā€™s a *resizable* sidebar on macOS, something I was honestly too lazy to implement. Yay!

Agreed. I bailed on attempting to roll my own recently, glad I waited.

One more thing I missed: Mergable libraries. The flexibility of pre-built frameworks, the final file size of static libraries? Yes please?
 

Andropov

Site Champ
Posts
615
Reaction score
773
Location
Spain
I generally take more tact with my team members, but that's what I'm thinking when I type out "this seems overengineered", "why are we doing it this way? X makes more sense", etc.
I wish this was more common. I've found that it's much more difficult to convince other developers that things should be simpler than it is to convince them that things should be more complex. Two weeks ago, I spent several hours trying to fight another developer that had devised an incredibly overengineered architecture for the models: they wanted models to be immutable (okay, I guess), but instead of using a struct (and letting the compiler figure everything else out, they had implemented every model as a class where every single property was declared let, plus a builder pattern on top of it.
I still have some code from the discussion around:
Swift:
class FooModel {

    let fooPropertyA: String
    let fooPropertyB: String
    let fooPropertyC: Int

    private init(builder: Builder) {
        fooPropertyA = builder.fooPropertyA!
        fooPropertyB = builder.fooPropertyB!
        fooPropertyC = builder.fooPropertyC!
    }

    class Builder {
        fileprivate var fooPropertyA: String?
        fileprivate var fooPropertyB: String?
        fileprivate var fooPropertyC: Int?

        func set(fooPropertyA: String) -> Self {
            self.fooPropertyA = fooPropertyA
            return self
        }

        func set(fooPropertyB: String) -> Self {
            self.fooPropertyB = fooPropertyB
            return self
        }

        func set(fooPropertyC: Int) -> Self {
            self.fooPropertyC = fooPropertyC
            return self
        }

        func build() -> FooModel {
            FooModel(builder: self)
        }
    }
}
When I asked what on earth was that, basically rolling out their own flavor of value semantics, I discovered that they had made it like that to enforce models being passed around by reference instead of by value, as they considered the cost of copying (the stack size of) structs unacceptable. I couldn't convince them that this was all unnecessary (and actually worse in a few more important things: it's non-Sendable as it's not declared final, it force-unwraps optionals...). They took great pride in what they had architected.

Yup, honestly the fact that I just created a class with composition and it mostly figured everything out to represent it in CoreData properly is yuge. Predicate macros help a ton as well since you describe predicates in terms of your model types. Just need to convince them to fix the bugs.

This is what I wanted 2 years ago to be honest.
Type-safe predicates are a great improvement here too. Using string-based values for it felt so wrong in Swift. It's relatively minor compared to all the other changes, but I'm glad it's here. I love how Swift is slowly but surely adding elegant ways to handle older, unsafe ways of doing things.

Agreed. I bailed on attempting to roll my own recently, glad I waited.
I couldn't replicate the entire functionality. I was missing the half-modal sheet on iPhones, for instance, although I had been meaning to implement that too. Seems that it's going to be extra easy :)

One more thing I missed: Mergable libraries. The flexibility of pre-built frameworks, the final file size of static libraries? Yes please?
I always get a bit lost with these things. I don't know much about static vs dynamic linking, other than the very basics. I've never had to worry about that (yet?). I'll watch the talk.
 
Top Bottom
1 2