“The core problem, in my opinion, was Combine was not updated and that’s bananas.”
Oh boy. The perils of believing Apple when they say this is the next great thing.
@woolie I don’t fully understand what this means. Can you explain a bit more?
@mattiem @woolie UIKitDynamics. Amirite? I’m still a little annoyed how much time I spent learning that one. :)
But it should have been (and was to me) obvious that Combine was transitory, no matter what the sessions said. No `throws`. Without `throws`, it’s not Swift.
But I don’t recognize the specific quote.
@cocoaphony @mattiem @woolie I felt so inadequate having glanced at the api and not being able to come up with ideas on how to use it effectively.
@Migueldeicaza @mattiem @woolie bizarrely, it is still IMO the best available way to observe NotificationCenter. I wish it weren’t. Async/await needs to fully engage with these use cases and Apple needs to officially document recommended patterns using it. But here we are.
I generally like FRP conceptually (though I‘ve found it hard to debug in practice on most platforms). I think Combine is a good implementation and seems pretty well written. It just isn’t good Swift.
@cocoaphony @woolie The quote is me.
Swift decided to default closures to non-Sendable. This has major compatibility consequences. One is library developers pretty much have to annotate their code. Here, Apple did not and I think that was irresponsible.
@mattiem @cocoaphony @woolie I googled the discussion, it is a good one:
https://mjtsai.com/blog/2024/10/01/swift-concurrency-and-objective-c/
(You need to follow the links, but the background there is good)
@mattiem @woolie and I do agree with that. Apple has played an obnoxious game with Combine by treating it as “soft deprecated” which is to say “abandoned” without saying it out loud.
They either have to wind it down or provide some minimal support. Instead they’ve created a public hazard as it rots and falls apart. When you build something that big, it comes with responsibilities.
@cocoaphony @mattiem @woolie Agreed.
A while ago, I did some consultancy work on an app that was entirely built with Combine (pre-Swift concurrency).
The original developer wanted to future-proof the app and believed that using Combine was the solution.
However, it’s so embedded in the architecture that the only way to remove it would be through a complete rewrite, which the small manufacturing company doesn’t have the resources for.
Apple has a responsibility to at least maintain it.
@cocoaphony @mattiem @woolie Even then, there are use cases from Combine that Swift Concurrency does not fufill. I use Combine for a major part of the data flow in my software (wrote that part when Combine wasn’t soft-deprecated). It still works, but now I try not to expand the use of Combine, but I also cannot rewrite it easily using Swift-native tools (particularly single value producers with multiple subscribers or timing like `.collect(.byTime(RunLoop.main, .seconds(0.1)))`).
@florian @cocoaphony @mattiem @woolie We used Combine extensively at my last job to consume Firebase listeners in sometimes complex ways. Combine’s flexibility and ease of use made us very reluctant to rewrite it all with AsyncStream. It’s a bit frustrating that Apple has left it in an ambiguous state without a clear successor.
@cocoaphony @mattiem @woolie @ivanopcode the problem is you can’t fix Combine and remain source-compatible, and even then, a “fixed” Combine has some pretty big limitations, and eg. `.assign(to:)` is unimplementable.
For better or worse, AsyncSequence is the future of Combine. And Apple *did* provide the Combine APIs to handle that interoperability.
It sucks, NGL. But their hands are pretty tied by the incredibly poor mismatch between Combine and strict concurrency.
@OneSadCookie @cocoaphony @woolie @ivanopcode I know it cannot be fixed. But I *think* expressing reality via annotations would have made the problems a lot more obvious to users in a way that would have helped avoid interoperability issues.
@mattiem @cocoaphony @woolie @ivanopcode I think the problem is that reality can’t be expressed:
Just(3).map { $0 }
-> func map(_: (Int) -> Int)
(Everything’s synchronous)
Just(3).receive(on: DispatchQueue.main).map { $0 }
-> func map(_: @MainActor (Int) -> Int)
(Now we’re async but we know the actor)
Just(3).receive(on: DispatchQueue.global()).map { $0 }
-> func map(_: @Sendable (Int) -> Int)
(Now we’re async off all global actors)
@mattiem @cocoaphony @woolie @ivanopcode That is, the type of map (and most of the combinators) depends on the prior elements in the chain. Mostly this *is* expressible in Swift, *if* you could add additional associated types to Publisher… but that breaks source compatibility (everyone uses AnyPublisher, even if they don’t explicitly state any other publisher types)
@OneSadCookie @cocoaphony @woolie @ivanopcode Yeah this is quite similar to the problem dispatch faced. Just gotta make them all Sendable. It’s very imprecise, but I think it is accurate.
@mattiem @cocoaphony @woolie @ivanopcode I guess we can disagree about what “accurate” means — `@Sendable` on everything is overcautious, but safe. It just means that plenty of existing, correct Combine code would no longer compile.
Other problems it doesn’t solve: `assign` can’t be made safe (just needs to be obsoleted in favor of sink), and the safety contracts of the Subscriber, etc. used to implement custom publishers, can’t be expressed.
@mattiem @cocoaphony @woolie I’ve thought about this, and annotating combine “correctly” is particularly challenging. A common combine use case is synchronous on its calling queue, (e.g. subjects publish immediately and synchronously) and “safe” with non-Sendable closures. Once you throw in an operator that changes the scheduler, the closure would have to be Sendable. It would need major changes to express that in its API, since Publishers don’t expose their target scheduler.
@mattiem @cocoaphony @woolie Forcing every closure to be @\Sendable would be most correct, but would break many common uses of combine that depend on the synchronous use case.
@pretz @cocoaphony @woolie You are completely right. Big problem with lots of trade-offs.
@mattiem @pretz @cocoaphony @woolie beyond Combine there are several other APIs that just aren’t compatible with swift concurrency and will ultimately need to be replaced. (e.g. NotificationCenter) this will be a long and painful transition
@calicoding @mattiem @pretz @cocoaphony @woolie I’m not sure if even the most basic UI things like UI/NS table view diffable data sources are compatible with concurrency. I use them everywhere.
The one thing that makes me hesitate most to dive into Swift 6 is that I don’t believe Apple can handle migrating their code to work with it. I really can’t tell if this “transition” that everyone keeps talking about will actually ever happen or not.
@pasi @calicoding @pretz @cocoaphony @woolie I have yet to encounter a system that actually cannot be used. It’s always possible, but it does occasionally require using one of the unsafe opt outs.
@mattiem @calicoding @pretz @cocoaphony @woolie But is it supposed to remain that way? Or are these all things we need to report to Apple about? And if we report will they actually get fixed.
These are all questions I thought I knew the answer to but don’t anymore.
This is based on watching from the sidelines how it’s all been playing out, and on some replies from people working at Apple. So just my impression, nothing more.
@pasi @calicoding @pretz @cocoaphony @woolie Not all APIs will make this transition. There’s enough interoperability features so that everything is *usable*. But some things, particularly those with concurrent behaviors that cannot be modeled with Swift (like Combine), are unfixable. That doesn’t mean they can’t get better/less painful though.
@mattiem @calicoding @pretz @cocoaphony @woolie Then we get to the question if this is a net benefit for something like iOS app development. If the existing APIs remain difficult to use or we must do without them (Combine).
I can see other areas where we need to be more strict and this extra concurrency safe mode is called for (server side code?). But is it something we need to be pushing on all iOS app developers?
I know it’s optional, but many will not see it that way and will try to do everything they can to get their code base to Swift 6, even if that’s at the cost of readability.
It feels like we’re dangling Python 3 in front of Python 2.7 developers and telling them it’s completely optional to move to it at this point.
@pasi @calicoding @pretz @cocoaphony @woolie I'm never 100% sure what to tell people here. I understand feeling pressure.
I kind of regret not waiting a little longer. Swift 6 obsoleted quite a bit of work that was necessary only for 5.10. I tend to think of this release more like concurrency 1.0.
@mattiem @pasi @pretz @cocoaphony @woolie check out AVCaptureSession. The docs are clear that it’s startRunning() method shouldn’t be called on the main thread, but AVCaptureSession isn’t Sendable. Ultimately you want to add it to AVCaptureVideoPreviewLayer, which isn’t actually MainActor isolated, but it’s also not Sendable. So I’m not sure how to assign that layer to a view. But maybe region based isolation would help here
@calicoding @mattiem @pasi @pretz @woolie I think the recurring theme is that Apple refuses to acknowledge known bugs or provide roadmaps. If we knew this will be fixed in the next release, that would be completely different than if it's some deep problem that we need to develop a workaround, which is different than if Apple intends to give us a new tool, which is different than "you're holding it wrong."
But Apple refuses, so we have to make guesses, often wrong, but all we have.
@cocoaphony @mattiem @woolie that was a fun framework, I wish we had SwiftUIDynamics
@cocoaphony @mattiem @woolie yeah, I agree on combine. But not sure about UIKitDynamics, i bet it didnt evolve much, but what to use as alternative?
@mattiem it means that Apple abandoned Combine without a word. Left all that developer effort by the wayside as an evolutionary dead end.
I agree with you it is bananas. I just didn’t want to call you out by name.
Does that explain it?
@woolie Totally. Thank you!