Short description of the issue:
iOS 13/mac OS 10.15 come with built-in reactive programming framework:
https://developer.apple.com/documentation/combine
https://developer.apple.com/documentation/combine/publisher
Expected outcome:
RxSwift should be updated to use the built-in primitives where possible, or at least provide a set of adapters.
What actually happens:
RxSwift doesn't use Combine framework
Platform/Environment
Xcode version:
11.0+
Irrelevant. RxSwift supports iOS 8 and above. Combine supports iOS 13 and above only.
Sorry, closing was accidental. Let me know if you have any other notes or thoughts here.
iOS 13/mac OS 10.15 come with built-in reactive programming framework:
I'll look into this.
Interesting, didn't know that.
Yeah, @kzaher - it was just released in beta. They made their own RxSwift-look-alike :)
I think it might be worth providing an interface that looks exactly the same as Combine that works with older versions of iOS/macOS.
You can summarize these possibilities with a regular expression: value*(error|finished)?
๐
Where did we see this before :)
They've also copied the stupid mistakes we did:
func retry() -> Publishers.Retry<Self>
Performs an unlimited number of attempts to recreate the subscription to the upstream publisher if it fails.
๐
What do you think about:
if #available(iOS 13, *) {
*implementation using Combine*
} else {
*current implementation*
}
Irrelevant. RxSwift supports iOS 8 and above. Combine supports iOS 13 and above only.
Sure, we can't change the internal implementation to only use Combine right away, however, at some point we'd need to transition, if ever. Maybe it would be best to be able to toggle between implementations so that it's not a huge change all at once.
Switching to Combine would also probably greatly improved debugging. ๐ค
โAt some pointโ is the tricky party. It will require RxSwift to be minimum version of iOS 13 - that would take years, if at all. I donโt think this library has any interest to increase the minimum deployment target in the foreseeable future.
@freak4pc what do u think about splitting implantation depending on iOS version?
I personally don't see much value in doing that. That framework could change a million times from now to when we raise iOS 13 as the minimum... not sure what value it brings to the table.
If someone wants to use Combine, they can just do that. I'm not entirely sure why RxSwift should use it under-the-hood. In my understanding there are still a lot of missing pieces to get everything done here.
It seems to me that Apple's Combine concept is equivalent to Observable. Naysayers will say, but it has generic error type and structs, but I don't think it matters much (like I can easily demonstrate with my typealiases excercise ๐).
They've also added opaque result types a couple of months ago
https://github.com/apple/swift-evolution/blob/master/proposals/0244-opaque-result-types.md
which creates a drastic change in the way protocol based code can be consumed.
We'll see how this will develop in the future, but as I see it right now it seems to me that we can make the core of the library by just typealiasing their Combine types.
typealias Observable = Publisher
typealias Disposable = Cancellable
typealias Observer = Subscriber
typealias ConnectableObservableType = ConnectablePublisher
typealias SubjectType = Subject // :)
typealias Subject = PassthroughSubject
typealias BehaviorSubject = CurrentValueSubject
typealias SchedulerType = Scheduler
Now this is a design flaw IMHO
https://developer.apple.com/documentation/combine/immediatescheduler
You can only use this scheduler for immediate actions. If you attempt to schedule actions after a specific date, the scheduler produces a fatal error.
They could have made Scheduler conform to ImmediateScheduler like we have. They'll probably change this.
Is there some value in having a library that's just typealiases? ๐Don't know. Probably not much, but we can do it easily ๐
I don't understand why they've made it reliant on the lastest OS and didn't support Linux.
They still didn't add Single and other traits, but this can be easily fixed. Out traits actually don't implement any operator, they just wrap the Observable operators.
I think that this is actually awesome news.
I think that RxSwift 5.x should work with Swift compilers for the next foreseeable future (3-4 years).
This means that we can make 6.x a tiny typealias/wrapper only library around Combine which just adds traits. Probably not a lot of people will use it, but there won't be any significant maintenance burden for us.
We also wouldn't have to worry about backwards compatibility since 4.x/5.x is battle tested and people who don't want to invest time in upgrading can just continue using it.
I'm excited about the possibility of just deleting 80000 lines of Swift code that was iterated over the past 5 years through various iterations of the Swift compiler. We've first started before protocol extensions were added. Does anybody even remember the >- operator ๐.
I'm happy for the broader Rx community and for all of the state machines of the world. I've been only a small part of this story. It is surprisingly difficult to convince developers that state machines which produce values can enter a fatal error state out or enter a final complete state and that it is worthwhile to explicitly model this ๐.
That's the nice part. The bad part is that I feel like people are usually using this concept in naive way and probably messing up their systems. It will be interesting to see how will this story will evolve over time.
This means that we can make 6.x a tiny typealias/wrapper only library around Combine which just adds traits. Probably not a lot of people will use it, but there won't be any significant maintenance burden for us.
That would mean you'll have to make 6.x minimum version of iOS 13 / macOS Catalina, and no Linux. Not sure it's the best idea :) Or - basically have #if available everywhere :)
Does anybody even remember the >- operator ๐.
Forever, in our hearts. โค๏ธ
That would mean you'll have to make 6.x minimum version of iOS 13 / macOS Catalina, and no Linux. Not sure it's the best idea :) Or - basically have #if available everywhere :)
Well, 5.x will work on Linux and I don't see any reason why they wouldn't support linux with the Combine library in the future. This is just beta.
5.x should work for the foreseeable future.
I don't think we would make these changes now, but if we are talking about a year period, I think it should be possible. The usual mantra is to support current and current - 1 version of the iOS.
These are my thoughts. I don't think it makes sense to compete with Apple on this. It's awesome they want to maintain this. Less work for us.
I'm trying to dig behind the scenes and see if RxSwift can be bound to and/or drive SwiftUI components. Not quite sure yet if @State annotations are using Combine behind the scenes.
@hmlongco from what I can tell, SwiftUI leveragesBindableObject which requires a PublisherType from Combine: https://developer.apple.com/documentation/swiftui/bindableobject
Just wanted to join in and share an opinion, this is not backed by facts:
Introduction of Codable rendered almost all the Swift JSON libraries out there obsolete, since it solved the base case really well, with minimal boilerplate. You'll still fine developers using 3rd party libraries for JSON, but I believe it has become a niche.
This is also reminiscent of the discussion SpriteKit sparked, given its resemblance to Cocos2d (albeit, that was an entire game engine). There was no winner here (IMHO, see above). Cocos2d is open source, mature, and I personally found it more performant. Of course, strong case to use SpriteKit exists as well.
Regarding RxSwift, I absolutely love it, and it's integrated in all private modules and submodules in my personal projects, but I can't make myself use it at work or in public modules. Minimal dependency school of thought prevails for me in these cases.
_Conclusion_
I don't see myself ever removing RxSwift in favor of combine, but I definitely see myself using Combine in public modules and working with large teams. This makes the already mentioned idea of introducing adapters so the translation becomes painless very compelling to me.
@Mazyod As mentioned above, SwiftUI has baked Combine into its DNA, as it's the primary mechanism used to trigger UI changes and updates.
As such, I'm afraid it's going to be rather difficult to avoid.
I should note that one thing I haven't seen yet is Combine integration into many services like Notifications, Timers, etc..
@kzaher Just regarding retry() and schedulers - if you feel like Apple have repeated mistakes that the Rx community have learned from please try to let them know! The new APIs are in beta and I'm sure they would be open to feedback from core contributors of the reactive frameworks that clearly inspired Combine. Potentially @lukabernardi?
Hey all, as a starting point, I wrote a quick reference blog post to outline the "direct converts" of RxSwift to Combine. This might help some of you experiment with it. If you have any feedback please feel free to share with my in the post, or DM me on Slack/Twitter :)
Some community members such as @hmlongco helped with some of the details, so thanks for that !
https://medium.com/gett-engineering/rxswift-to-apples-combine-cheat-sheet-e9ce32b14c5b
Use Combine to implement RxSwift is not making sense. Maybe more and more people will migrate from RxSwift to Combine as the iOS 13 installation base increases, so RxSwift here can be a quite well transition solution. What I mean is, bump a version and modify the API design to match Combine. In that way, when developers want to adopt Combine while they cannot set the minimum SDK to 13, they can choose RxSwift temporarily as a "community version" Combine. When it's okay to fully switch to Combine, the process can be more smooth.
bump a version and modify the API design to match Combine.
Why would we do this? RxSwift follows ReactiveX spec, not Apple spec :)
If anyone wants to make a Combine-polyfill based on RxSwift, I believe that's rather possible.
I find it hard to believe RxSwift will move away from the spec as that's one of the key points and guarantees of this framework.
@freak4pc Yes, you are right. RxSwift's core design is following ReactiveX. And considering this, I think there is no necessary to make changes.
I agree. As of now there will be no change - but if we can, later on, leverage Apple's own core framework (Combine) to reduce some of this library's foot print, that's a pretty good benefit. Less maintenance and tests needed for us :)
So I think that - long term plan here is: Once this repo is ready to increase minimum deployment target to iOS 13 etc, we can keep the existing interface mostly, but change the underlying implementation to use Combine, since the similarities are quite high.
I think of the other way around: in order to support gradual migration, it could be helpful to provide wrappers. E.g. wrap RxSwift Observables in Combine Publishers, so an unmodified RxSwift model layer could continued to be used with SwiftUIโs BindableObject (during a transitional migration period). And maybe the other way around too, wrap Combine Publishers in RxSwift Observables so the new DataTaskPublisher can be used. Then clients can replace more and more parts of a subscriber chain without a big bang release.
@fabb I don't think this is a possible plan, but if you have some basic MVP we can consider it.
SwiftUI bases much of its implementation details on things related directly to Combine usage - I'm seeing very little value to making RxSwift provide some API as it's correlation with SwiftUI is very minor without adding a lot of custom property wrappers. The value in correlation to SwiftUI is even lessen since it has the same deployment target as Combine itself.
Maybe RxSwift could include some wrappers to create Publisher from Observable , Subscriber from Observer etc so that projects which currently use RxSwift could in future combine those with Combine
As we speak there are iOS programmers out there that are literally scared by Rx but they are going to give Combine a try since "it's Apple's framework and we won't have to learn Rx"!
Previously we could have these developers use Rx if they just understood how great it was but now we will most certainly "lose" them to Combine.
I also agree and echo the earlier thoughts about JSON parsing and Apple's own implementation which rendered dragging in a third party pod to do your JSON parsing almost obsolete. If you're using Apollo then you don't even have to use Apple's parsers!
What I'm getting at is: people who are new to Rx programming are going to eventually ask the question: why use Rx when I can use Combine and we should have an answer to it!
Have a great day
@fabb This makes a lot of sense. As someone mentioned, one of the authors of SwiftNIO mentioned that Combine is based on the Reactive Streams standard.
https://en.m.wikipedia.org/wiki/Reactive_Streams
Very similar to RxSwift but with some emphasis on resolving back pressure issues.
A java version using the same standard was included as part of Java9.
When introduced, to ease the transition from an alternative reactive implementation called Flow, a set of adapters were created that allowed the two standards to inter operate and facilitate a gradual migration.
Iโm not sure if it will be as simple between RxSwift and Combine, taking into account the back pressure additions and additional error type, but it seems like it could be feasible.
@tcldr
Please correct me if I'm wrong, but it seems to me that Reactive Streams only describe that there is observer + backpressure:
Everything else are "Rx" concepts as far as I can tell (Subjects, Schedulers, particular operators ...)? RxJava also conforms to Reactive Streams specification.
It was my impression that Reactive Streams is about interoperability between different implementations and not about details of particular implementation.
The only reason why I'm mentioning this is because people think that there is some standard describing Combine framework, but I don't think there is as far as I can tell.
Maybe I'm mistaken here so if somebody can add some references, that would be great.
@kzaher
Yes, I think that's correct. I haven't spent much time looking into it, but if I'm understanding correctly the reactive streams specification simply introduces the concept of back pressure handling.
With this addition to the contract a Publisher is acting illegally if it sends more elements than have been explicitly signalled by the subscriber.
That's the only material difference afaik.
I think it would be relatively straight forward to produce bridges that run in either direction that would handle the back pressure legally, and then RxSwift and Combine (if it is indeed a strict reactive streams implementation) should be able to interoperate freely.
I mean I understand their rationale (complete control over the code base, security etc.) but I still cant for the life of me understand how anyone could get the idea of implementing a framework that does less of the same thing as a mature and open source framework already out there in the wild with a ton of adoption past their financing department.
That said I don't think Making RxSwift a backwards compatibility tool for Combine is a particularly good idea.
I look forward to RxSwiftUI ... less so if I have to write it myself.
@freak4pc secretly cooking? https://github.com/freak4pc/RxCombine
@fabb Sharing a repo with thousands of people is hardly a Secret ;D
@agiokas - You can achieve what you asked for in my RxCombine library which is in the comment above this
@tcldr - I'd like to add legal backpressure conversion from RxSwift to Combine (in RxCombine) but it is quite tricky - I'm on a holding stance until things become more clear on Combine - how it works, what are the guarantees, is there any thread safety, etc
@kzaher
They still didn't add Single and other traits, but this can be easily fixed. Out traits actually don't implement any operator, they just wrap the Observable operators.
Future is like single :)
https://developer.apple.com/documentation/combine/publishers/future
@alinekborges
Future is like single :)
It's an attempt, but Combine.Publishers.Future has the usual promise/future failed design elements (no cancellation, imperative evaluation, ...). Practical code snippets are underneath.
I don't understand why on earth have they done this.
Here are some practical differences (at least from what I can see on my machine with Xcode Version 11.0 beta (11M336w)).
let single = Combine.Publishers.Future { (observer: @escaping (Result<String, Error>) -> Void) in
print("This will be only printed once") // Single will do retries properly
observer(.failure(NSError(domain: "", code: -1, userInfo: nil)))
// No cancellation!
}
_ = single
.retry()
.sink(receiveValue: { print($0) })
Combine.Publishers.Future { (observer: @escaping (Result<String, Error>) -> Void) in
print("This will be only printed once") // Single doesn't print this without subscription, it's just a definition.
observer(.failure(NSError(domain: "", code: -1, userInfo: nil)))
}
Combine.Publishers.Future { (observer: @escaping (Result<String, Error>) -> Void) in
print("This will be only printed once")
observer(.failure(NSError(domain: "", code: -1, userInfo: nil)))
}.first() // Single only supports operations that make sense, and not all Observable operations.
Combine.Publishers.Future { (observer: @escaping (Result<String, Error>) -> Void) in
print("This will be only printed once")
observer(.failure(NSError(domain: "", code: -1, userInfo: nil)))
}.map { $0 * 2 } // Operations on `Single` which don't change the number of elements have result type Single and not Observable (Publisher) like `Future` does
It seems to me it _should_ allow cancellation, if it has a custom Subscription that handles that Cancellable. In general, doing resource management in Combine seems much more verbose and involved when comparing to our good-ol' DisposeBag and flatMapLatest.
I don't understand why they've made it reliant on the lastest OS and didn't support Linux.
I think, since the Swift ABI is stable, Combine is a Swift framework on the user's iOS system. So its least supported version is iOS 13.
What I don't fully understand is why receive(Input) returns a demand. The Reactive Streams design uses the fire-and-forget principle so callers don't have to wait and implementors don't have to block or juggle some value to return. If it is for request management, that's what Subscription.request is for, which can be called synchronously or asynchronously.
Also, does Apple release the source code for such frameworks?
@akarnokd
Also, does Apple release the source code for such frameworks?
Sometimes, if you are lucky. https://github.com/apple
It's highly unlikely in this case, though /:
Which also means Combine under Linux is not realistic, unless we'll get some surprise around this.
I'm closing this for the moment. Seems like a far-fetched dream, many many years away :)
Most helpful comment
It seems to me that Apple's
Combineconcept is equivalent toObservable. Naysayers will say, but it has generic error type and structs, but I don't think it matters much (like I can easily demonstrate with my typealiases excercise ๐).They've also added opaque result types a couple of months ago
https://github.com/apple/swift-evolution/blob/master/proposals/0244-opaque-result-types.md
which creates a drastic change in the way protocol based code can be consumed.
We'll see how this will develop in the future, but as I see it right now it seems to me that we can make the core of the library by just typealiasing their
Combinetypes.Now this is a design flaw IMHO
https://developer.apple.com/documentation/combine/immediatescheduler
They could have made
Schedulerconform toImmediateSchedulerlike we have. They'll probably change this.Is there some value in having a library that's just typealiases? ๐Don't know. Probably not much, but we can do it easily ๐
I don't understand why they've made it reliant on the lastest OS and didn't support Linux.
They still didn't add
Singleand other traits, but this can be easily fixed. Out traits actually don't implement any operator, they just wrap theObservableoperators.I think that this is actually awesome news.
I think that RxSwift 5.x should work with Swift compilers for the next foreseeable future (3-4 years).
This means that we can make 6.x a tiny typealias/wrapper only library around Combine which just adds traits. Probably not a lot of people will use it, but there won't be any significant maintenance burden for us.
We also wouldn't have to worry about backwards compatibility since 4.x/5.x is battle tested and people who don't want to invest time in upgrading can just continue using it.
I'm excited about the possibility of just deleting 80000 lines of Swift code that was iterated over the past 5 years through various iterations of the Swift compiler. We've first started before protocol extensions were added. Does anybody even remember the
>-operator ๐.I'm happy for the broader Rx community and for all of the state machines of the world. I've been only a small part of this story. It is surprisingly difficult to convince developers that state machines which produce values can enter a fatal error state out or enter a final complete state and that it is worthwhile to explicitly model this ๐.
That's the nice part. The bad part is that I feel like people are usually using this concept in naive way and probably messing up their systems. It will be interesting to see how will this story will evolve over time.