GitXplorerGitXplorer
r

RXFutures

public
88 stars
0 forks
0 issues

Commits

List of commits on branch develop.
Unverified
b05578af2f62b71ef1df7136a199ccaea9d3e0c8

Futures free their handlers as soon as they’re completed/cancelled.

rrobrix committed 12 years ago
Unverified
59e8ea1d55ad2c417daa6aa2325cfe38d3cf993d

Fixes an ARC incompatibility.

rrobrix committed 12 years ago
Unverified
7eeb302f558634dae38f8896b314f23ad5d5c00b

Uses serial queues instead of global concurrent queues to prevent mass thread spawn.

rrobrix committed 12 years ago
Unverified
6a17ea569efd9f8f46567cfb46504408ada48a4e

Futures can pass control to other futures.

rrobrix committed 13 years ago
Unverified
161bb79c1eb7eba92bfacfff31f056da583e2726

Adds some notes to the readme about some of the more recent conveniences.

rrobrix committed 13 years ago
Unverified
15221ff3079cfad820c1f0fa1365d5024117ad64

Separates MRR and ARC into their own targets from the same sources.

rrobrix committed 13 years ago

README

The README file for this repository.

#What

Futures are a way to represent the expected result of some asynchronous action. RXFuture is a class for handling cancellation and completion and the notification of either for asynchronous tasks.

#How

Let’s start with a simple asynchronous worker which calls a block with its result when it’s done:

-(void)workWithCompletionHandler:(void(^)(id))block {
	dispatch_async(dispatch_get_global_queue(DISPATCH_PRIORITY_DEFAULT), 0), ^{
		// work work work
		block(@"all done!");
	});
}

So far so good. We’d like to be able to cancel it, though. Let’s start by using a future to handle completion notification:

-(RXFuture *)workWithCompletionHandler:(void(^)(id))block {
	RXFuture *future = [RXFuture new];
	[future onComplete:^{
		block(@"all done!");
	}];
	dispatch_async(dispatch_get_global_queue(DISPATCH_PRIORITY_DEFAULT, 0), ^{
		// work work work
		[future complete];
	});
	return future;
}

Now the caller has something to cancel:

@synthesize future; // assume this exists

-(IBAction)startWorking:(id)sender {
	[self showIndeterminateProgressBar];
	
	self.future = [worker workWithCompletionHandler:^(id result) {
		// we got a result!
		[self hideIndeterminateProgressBar];
		self.future = nil;
	}];
	
	[future onCancel:^{
		// clean up anything that might depend on what we attempt to do in the completion handler
		[self hideIndeterminateProgressBar];
		self.future = nil;
	}];
}

-(IBAction)cancel:(id)sender {
	[future cancel];
}

So far, so good—when the user cancels, we clean up the UI as necessary safe in the knowledge that the completion block won’t be called on us. The correct block will be called when the work completes successfully and when the user cancels it early. But the worker doesn’t actually cancel anything; that’s wasteful.

RXFuture supports multiple completion and cancellation handlers; it will call all of the completion handlers when it’s completed, and all of the cancellation handlers when it’s cancelled. You can take advantage of this to have your worker stop when it’s cancelled. Here’s an example that cancels an NSURLConnection when the future is cancelled:

NSURLConnection *connection = [NSURLConnection connectionWithRequest:request delegate:self];
[future onCancel:^{
	[connection cancel];
}];

If you’re implementing an asynchronous worker that needs to pass the result of some calculation to its caller via a block, it’s perfectly valid to call -onComplete: immediately before (or as you’ll see below, after) calling -complete.

id result = …;
[future onComplete:^{ block(result); }]
[future complete];

As a convenience, -complete: and its cancellation analog -cancel: (note the colon!) act as shorthand for this:

id result = …;
[future complete:^{ block(result); }]

If a completion or cancellation block is added after the future is completed or cancelled, it will be dispatched immediately:

[future cancel];
[future onCancel:^{ NSLog(@"this block will be run because the future has already been cancelled."); }];

Work that needs to be done on either cancellation or completion can be handled with -onCleanUp:. It will be called exactly once, when the future enters either of those states:

RXFuture *future = [worker startDoingSomething];
id workerDependentObject = [… new];
[future onCleanUp:^{ [workerDependentObject tidyYourselfUpALittle]; }];

#Details

It’s worth being aware of the specific semantics of RXFuture:

  • completion handlers aren’t called when the future is cancelled
  • cancellation handlers aren’t called when the future is completed
  • cleanup handlers (added with -onCleanUp:) are called exactly once when the future is cancelled OR completed
  • multiple calls to -cancel will result in each cancellation handler being called exactly once
  • multiple calls to -complete will result in each completion handler being called exactly once
  • completion handlers added after completion will be dispatched immediately
  • cancellation handlers added after cancellation will be dispatched immediately
  • if your completion handler requires a result that you don’t have when the task is started, it’s okay to call -onComplete: with the appropriate block just before (or after!) calling -complete
  • for the obvious reasons, only workers should call -complete on their futures unless both are designed to handle this; both clients and workers can both call -cancel, however
  • calls to -cancel and -complete are serialized; the first one dequeued wins
  • don’t call -isCompleted and -isCancelled except within a block passed to -performBlock:; in any other context, their return values are potentially obsolete before they’ve returned

#Thanks

Thanks to Andy Matuschak for some fascinating thought experiments that kicked this all off, and to Dave Dribin for discussion which refined RXFutures.