yup - you're happy not being able to serialise classes. That's great for you.
And you serialize data anyway, not logic. So why make yourself troubles then? Why do I need to serialize class with logic? Thatâs not Python and pickle
after all.
This is a fine way to approach things for the right app.
For my case - I'd rather have my doc as a class which is @Observable and which I can pass by reference. (Because modifying structs isn't always fun)
That would be a great world to live in. But if you want to display a model in Apple's UI system - then it is a concern of the model to ensure that it is only modified on the main thread.
You can either accept that (annoying) responsibility - or duplicate your data into a facade which does accept that responsibility.
right (or AsyncCodable if needed)
I do not understand how you are making this determination. Your use-case of an isolated class that is also created in the background is going to be extremely hard to pull off. I'm not willing to say flat out impossible, because I have not invesigated this exact thing closely.
But I'm going to predict that even if it is possible, by the time you get to Swfit 6 language mode, you won't want to do it because it would require too many compromises in the rest of your application's design to accommodate.
You are being unfair comparing eventual unsafe points in non-Swift and not annotated ObjC code eventual occurrence to pre-Swift 6 world where you have had zero compile time guarantees that your code is concurrently safe. And treat this as incompletion, while amount of safety gained by Swift 6 is tremendous.
The issue isn't background creation.
The issue is that I want to use an explicitly isolated class and also be able to serialize it.
The reasons for explicit isolation are:
- I prefer explict intent
- Being explicit (with swift6 and all warnings) has a better chance of warning me when I'm using code that would break the isolation.
so - now you're making an argument that we should remove codable from classes?
and presumably any struct which also contains logic?
I'm afraid this is not true.
You cannot write code that violates isolation without using unsafe-opt-outs or a non-Swift language. And when you do these things, the overly-explicit isolation will not save you.
Edit: just one more quick thing. Swift does have the concept of explicit/implicit isolation, but its meaning does not at all match how it is being discussed in this thread. I wanted to call that out because I'd like to try to limit the confusion as much as possible.
Typically you should only serialise types that are designed to be serialised in the first place; in 99% of usual cases these are so-called DTOs (data transfer objects), which are best modelled as structs. They could have some minor logic (like an accessor that converts degrees to radians, for instance), but preferably nothing with side effects.
My example above works the same if Doc
is a class. It doesn't need to be Sendable
, it's just that it's trivial to do once your document only holds data.
That is the world we live in. View Models and View Controllers need to enforce the Main Actor isolation. But the data (the models) inside those View Models and View Controllers doesn't. I encourage you to watch some of the WWDC talks on how to structure applications in the SwiftUI and Swift Concurrency world (the latest being Migrate your app to Swift 6).
I'm going to drop off of the discussion for now as I don't this is being productive. My last suggestion is to give those ideas a try, test a few options in a small sample project (maybe play around with Apple's own sample project from the talk I mentioned before) and get some experience with a strict-concurrency environment, even if you dislike it. At least you'll get a taste of what the vision is, even if there are some rough edges (which may not be where you expect them to be), and get some appreciation of why some things are done the way they are done.
But this point in the thread, it's very hard to separate fair concerns about the ergonomics of Swift Concurrency (ie isolated types conforming to non-isolated protocols in legitimate use cases) from other issues that mostly stem from trying to fight the language (which is a very frustrating endeavor), and trying to discuss both things at once is... not ideal.
You have troubles mixing isolated class type and using Codable
. I say that you can extract data out of it, and make it to conform Codable
, with no downsides whatsoever.
In a more broader sense, I'm on the same page with @nkbelov statement above.
Let's try to bring in another concept, that has a lot of similar properties as Swift Concurrency: Rust's borrow checker. Both ideas aim for the same goal: have compile-time guarantees of the code correctness in certain aspect.
Borrow checker makes sure that your code is free from dangerous memory access and controls lifetime of objects quite effectively. Can you still write unsafe code? Absolutely. You can opt-out with unsafe (and that's explicit), some find ways to write unsafe code despite borrow checker, and you almost always will be working with some C code, which is unsafe by definition (and which you'll bring into Rust with unsafe marks anyway). Does that mean Rust lacks something in its model? I doubt that.
This thread appears to become a bit heated, if I may say so.
Plus, it is kind of going in circles...
@ConfusedVorlon, I think you're being a bit unfair and overstating the "missing" of a Codable
-like protocol that you can adopt with a class like yours. As several others have said before, while this would be nice, the fact you miss it here is just a symptom of the approach you followed. And no, I absolutely do not mean that it's a bad approach, I have used it myself. Swift 6 now just "shines a light on it".
This "hole" you now see "a spotlight on" has always existed: You defined a class that is non-Sendable
, yet you constrain parts of it to @MainActor
. The fact you only see this result in warnings when adopting Codable
is a coincidence as you do not use it in a way you (explicitly) allow: Creating it in a background context and handing it over to the main UI thread. If you were doing this, you'd see more warnings, so this would have to be fixed in any way. However, with the way Codable
works (and has always worked), it implies you take care of even the possibility of it happening (otherwise Codable
's methods would be isolated).
The comparison between pre-concurrency era Swift and Swift 6 is inapplicable, as this has always been a dangerous approach. The language so far just lacked the features to point out a potential data race in your code that has always existed. In the past we just accepted this and defined it as the developers' responsibility to ensure the code would never be called in a way that triggered that race. And if you did do that, you never saw it at compile time.
Now the compiler can help, but the cost we pay is that we have to be explicit in some things to allow it to do so. That in some cases requires more than just adding annotations. You can't simply say "I want this type to not be Sendable
as I cannot easily write some form of locking, but I want these properties to be @MainActor
" without additional work (that others have proposed one way or another).
All this said, of course it would be nice it we had some magic tool that allowed us adopting Codable
to otherwise "under-specified" classes like this (i.e. classes that have the bare minimum @MainActor
annotations, but are not written to be necessarily Sendable
).
As was (again, already) pointed out, this is a community effort. As was Swift 6 and structured concurrency. Note that while Apple is a big player for Swift, that does not mean they're the ones who have to do it, or that it was "irresponsible" of them (or anyone, for that matter) to introduce the concurrency features without it.
Of course you're entitled to differ in opinion, but that's the way I see it.
Lastly I want to reiterate a suggestion that got drowned out a bit: @dynamicMemberLookup
to ease merging your class with a struct that holds your data. Have you really looked into that? I found it pleasantly easy and ergonomic when I had to pass around a custom class representing a file with its contents around isolation contexts recently.
Perhaps so - but we're going to be living with ObjC and unsafe packages for a long time. For example - as far as I can tell, CoreBluetooth absolutely calls back to protocols where explicit isolation would warn of issues.
Sorry about that. Are there better terms I should use?
yes - duplicating my data into a separate structure for serialisation would certainly solve my issue.
To me - there are clear downsides though!
(duplication & the need to write and maintain syncing code)
I wonder how many real-world indie devs live like this! I certainly don't.
And perhaps thats the answer. Swift 5 has let me be lazy and use a single model for serialization, simple logic, and powering the UI - swift 6 might force me (kicking and screaming) down a different direction (which may follow technical best practice more closely)
I think there would be an uproar if you tried to remove codable from classes...
You donât need duplication: Codable, SwiftConcurrency, Swift 6 - Fundamentally incompatible? - #19 by vns
On that note, I think we went on at least third circle in the topic, and I shouldâve stopped few tens of posts before
I think the comparison is applicable at the point where people ask developers to move from Swift5 to Swift6.
At that point - I don't just want to be told what I'm doing is potentially broken if I'm not careful - I want a good solution to do the job.
If I wasn't feeling the push to Swift6 coming down the line (perhaps mistakenly!) - I'd be very happy to keep my mouth shut and stick with Swift5.
at least I have learned that the answer is Yes
with a subtitle of "don't do it like that!"
Just to be clear - my focus isn't on whether an isolated type should conform to a non-isolated protocol. That seems implausible, but I don't have the expertise. I don't know what the implementation should be.
The thing I care about is practical usage.
Taking the Codable example - I don't care whether the class can conform to Codable. I care whether I have an ergonomic serialization system. (which could be AsyncCodable)
Just two cents - yes we meet difficulties when we try to use together the features of the language and the SDKs, and we sometimes hear from the compiler, and some peers, that we should adjust (the polite way to tell we don't do it right). That's tough.
One issue that wasn't met yet by @ConfusedVorlon is the difficulty to have a type that is both @Observable and Codable. I hope you weren't aiming at @Observable
in your next development steps.
That would be natural, though. The desire for @Observable
can come very quickly, once one has a mutable class. It's cool to have SwiftUI views automatically refresh. And that's how Apple's SwiftData models work: they are classes that are observable and obviously serialized. Not only it is difficult to foresee any trap, but we all make progress by copying designs: @Observable
+ Codable
is actually a honeypot.
But we can't compose everything as we wish today, and sometimes we lose a lot of time because we discover a dead-end late.
It's easy to complain â I do it all the time as well. Meanwhile, thank you for opening this thread, because it has been heard by the Language Steering Group. Until things change, adaptation is needed, and contributions for enhancing the language or the SDKs are welcome.
On a more personal note, I'd like to say that we do not always write professional-grade code that could be reviewed and vetted by the most drastic architects. Personally and in interaction with coworkers, it's common to write exploratory code, little proofs of concepts, tiny apps that should be written at the speed of thought. In this coding practice, which aims at efficiency, all the small hindrances are painful. This makes me able to consider some "adaptations" as slow-downs that could have been avoided. Long live to hackers!