Comments (22)
I want to spend some time thinking about this, but it's difficult without some examples of how cancelable signals are currently used.
I know we use them for shell tasks and git operations in GHfM, but are there more widely applicable use cases?
from reactivecocoa.
HTTP requests could be something you'd want to cancel. A length upload or download especially. More generally, any long-running asynchronous work.
When I talked over this with @xpaulbettsx a while back, he suggested that it should just be a matter of all subscribers disposing of their disposable.
I like the simplicity of that, but practically I'm not sure how that'd work. Cancelation probably comes from one place, while you could have n subscribers and those bitches don't known 'bout its cancelation.
Maybe cancelable things could be -multicast:
'd and then you'd just keep the disposable from -connect
around to use that to cancel? So there wouldn't be any formal idea of cancelation, just disposal.
from reactivecocoa.
That's a little scary to me, and seems to exacerbate the "conceptually weird" part, because you still have subscriptions that are terminating without completing or erroring, except now it's even more opaque and action-at-a-distance-y.
from reactivecocoa.
How would an FRP language (like Elm) handle stuff like this?
from reactivecocoa.
No idea. I haven't seen a similar concept yet. That's why I'm inclined to say we shouldn't include it.
from reactivecocoa.
FWIW one of the reasons that this came to being was the probable need for clean up when cancelling an operation, so simply disposing of the signal wouldn't solve that case.
What specific example we were basing that on I'm not completely sure.
from reactivecocoa.
Cleanup can always be handled with disposables, though:
- (id<RACSignal>)fetchData {
return [RACSignal createSignal:^(id<RACSubscriber> subscriber) {
NSOperation *fetchOperation = …;
fetchOperation.completionBlock = ^(NSData *data) {
[subscriber sendNext:data];
[subscriber sendCompleted];
};
return [RACDisposable disposableWithBlock:^{
[fetchOperation cancel];
}];
}];
}
from reactivecocoa.
What does it mean to cancel a signal? Does it complete? Does it error?
It could error with NSUserCancelledError
, but it would make subscribing to errors a bit more verbose if you have to handle it differently in many cases.
from reactivecocoa.
The question in my mind is, does it even make conceptual sense to talk about canceling a signal? It seems like a signal represents the result of the work, not the work itself. You cancel getting the result by disposing of your subscription. Canceling the work is an entirely different thing that seems separate from signals.
from reactivecocoa.
Yes, I see your point.
So you mean cancellation isn't something signals themselves should handle in general, but should be left up to the specific signal implementations.
I think this kind of separation between signals and "things that are related to signals but aren't signals" comes up with other FRP libraries too, in fact I have seen functions that return tuples of values, with one of the values being the signal, and the other values being related to it but used to interact with it in ways that wouldn't make sense for all signals, for example to send values to it or destroy it and so on.
// Returns a tuple where the first element is a signal that ...
// and the second element is a block that can be called to cancel the fetch operation
- (RACTuple *)fetchData {
NSOperation *fetchOperation = nil;
return [RACTuple tupleWithObjects:@[ [RACSignal createSignal:^(id<RACSubscriber> subscriber) {
NSOperation *fetchOperation = ...;
fetchOperation.completionBlock = ^(NSData *data) {
if (fetchOperation.isCancelled) {
[subscriber sendError:[NSError errorWithDomain:NSCocoaErrorDomain code:NSUserCancelledError userInfo:nil]];
return;
}
[subscriber sendNext:data];
[subscriber sendCompleted];
};
return [RACDisposable disposableWithBlock:^{
[fetchOperation cancel];
}];
}];, ^{
[fetchOperation cancel];
} ]];
}
from reactivecocoa.
I'm not sure a signal does just represent the result of your work. In that I don't think I like the idea of having to keep hold of multiple references which represent the same unit of work, merely to use them for different purposes.
from reactivecocoa.
@dannygreg You wouldn't have to keep multiple references around. The signal would be automatically kept around as usual as a consequence of having subscribers. You'd just need to keep the block or disposable or whatever around.
from reactivecocoa.
@Coneko I like that idea though I hate how awkward tuples are in Objective-C. At the very least, it seems we can agree that cancelable signals should die.
from reactivecocoa.
So do we all agree RACCancelableSignal
should 🔥?
from reactivecocoa.
from reactivecocoa.
Sure.
from reactivecocoa.
The question in my mind is, does it even make conceptual sense to talk about canceling a signal? It seems like a signal represents the result of the work, not the work itself. You cancel getting the result by disposing of your subscription. Canceling the work is an entirely different thing that seems separate from signals.
In a pure functional reactive world, you shouldn't really care how the work occurs or even if there is any (it could be mock data, for example). You just indicate that you don't want the results any more, and the environment infers that the operation should be canceled.
Now, it's arguably* true that there are UI cases where we need deterministic cancelation, so I think the answer is just to ensure that the semantics of disposables (with regard to canceling ongoing work) are well-defined, and then build or use methods like -takeUntil:
which help control disposal.
*Honestly, I'm not even convinced a UI needs to cancel immediately. "As soon as possible" is a well-established convention for software, and there's no guarantee that the user will cancel at the right time to avoid whatever side effects there are anyways.
from reactivecocoa.
Lemme make sure I'm understanding you.
You're saying that disposing of the subscription should cancel. So it's up to callers to coordinate with whoever else might subscribe to the signal and tell them to dispose when cancelation should happen.
Is that roughly right?
Conceptually I might agree. I'm worried that'd be onerous for users. I guess there's no reason they'd have to do that.
Here's a concrete example we can talk through:
RACSignal *signal = [self someCancelableThing];
self.disposable = [signal subscribeNext:^(id x) {
// do stuff
}];
// does a subscribe or 10, who knows
[self presentUIWithProgressForSignalOrSomeBullshit:signal];
[self.cancelCommand subscribeNext:^(id _) {
[self.disposable dispose];
}];
So now users have to know something about what -presentUIWithProgressForSignalOrSomeBullshit:
(or anything to which it passes the signal!) does and coordinate cancelation with it.
from reactivecocoa.
I would write the above as:
// Depends on -takeUntil: being guaranteed to dispose of the signal it subscribes to.
RACSignal *signal = [[self someCancelableThing] takeUntil:self.cancelCommand];
[signal subscribeNext:^(id x) {
// do stuff
}];
// does a subscribe or 10, who knows
[self presentUIWithProgressForSignalOrSomeBullshit:signal];
In this way, the code which gets passed signal
doesn't have to worry about cancellation, and control over when cancellation occurs is maintained at the point of creation.
from reactivecocoa.
@jspahrsummers Good point 👍
from reactivecocoa.
Although, looking at my code again, you would probably have to multicast signal
in order to avoid a race condition with cancelCommand
(if one of the hypothetical 10 subscribers only subscribes after cancelCommand
has already sent).
from reactivecocoa.
I'm spending some time reading through the Rx Design Guidelines, and section 4.4 seems relevant here:
When unsubscribe is called on an observable subscription, the observable sequence will make a best effort attempt to stop all outstanding work. This means that any queued work that has not been started will not start.
Any work that is already in progress might still complete as it is not always safe to abort work that is in progress. Results from this work will not be signaled to any previously subscribed observer instances.
from reactivecocoa.
Related Issues (20)
- [SwiftPM on Xcode] Package resolution failed HOT 2
- Unable to compile targeting macOS Catalyst using SwiftPM (fix exists)
- why RACObserve(self.scoreStepper,value) not available? HOT 1
- App rejected for HealthKit metadata HOT 4
- UISearchBar delegate proxy crash on Mac Catalyst HOT 1
- Build error when using ReactiveCocoa via Swift Package Manager HOT 3
- can not deinit HOT 2
- Xcode12 ReactiveObj archive error HOT 3
- How to implement PIN input with attempts HOT 1
- Dispose SignalProducer created via Action HOT 1
- UnsafeKVOProperty initializer crashes after updating to ReactiveSwift 6.5.0 HOT 1
- EXC_BAD_ACCESS Cash with NSURL HOT 1
- ReactiveCocoa 11.1.0 incompatible with ReactiveSwift 6.6.0 HOT 5
- Xcode 12.5 beta 3 can't build ReactiveCocoa with SwiftPM. HOT 2
- Using "<~" binding function with Signal.Observers causes memory leaks. HOT 1
- Upgrading from very old version (2.5) fails - can't find ReactiveCocoa.h HOT 1
- Cannot remove an observer <RACKVOProxy 0x280264940> for the key path "unit" from <HGConfigureModel 0x280d25050> because it is not registered as an observer.
- Current version can't be compiled with the latest ReactiveSwift version HOT 2
- Current version can't be compiled with the latest ReactiveSwift version HOT 6
- Add output values support for interception
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from reactivecocoa.