KEMBAR78
Promise of an API | PDF
Promise of an API
Maxim Zaks (@iceX33)
CocoaHeads Berlin
Agenda
— Consume a promise
— Produce a promise
— Swift
- (NSNumber*)addFirstNumber:(NSNumber*)number1
toSecondNumber:(NSNumber*)number2;
- (NSNumber*)addFirstNumber:(NSNumber*)number1
toSecondNumber:(NSNumber*)number2;
- (NSNumber*)addFirstNumber:(NSNumber*)number1
toSecondNumber:(NSNumber*)number2
withError:(NSError**)error;
- (NSNumber*)addFirstNumber:(NSNumber*)number1
toSecondNumber:(NSNumber*)number2;
- (NSNumber*)addFirstNumber:(NSNumber*)number1
toSecondNumber:(NSNumber*)number2
withError:(NSError**)error;
- (void)addFirstNumber:(NSNumber*)number1
toSecondNumber:(NSNumber*)number2
withBlock:(void (^)(NSNumber *sum, NSError *error))block;
- (NSNumber*)addFirstNumber:(NSNumber*)number1
toSecondNumber:(NSNumber*)number2;
- (NSNumber*)addFirstNumber:(NSNumber*)number1
toSecondNumber:(NSNumber*)number2
withError:(NSError**)error;
- (void)addFirstNumber:(NSNumber*)number1
toSecondNumber:(NSNumber*)number2
withBlock:(void (^)(NSNumber *sum, NSError *error))block;
- (void)addFirstNumber:(NSNumber*)number1
toSecondNumber:(NSNumber*)number2
success:(void (^)(NSNumber *sum))success
failure:(void (^)(NSError *error))failur;
And now with a real promise!
- (OMPromise*)addFirstNumber:(NSNumber*)number1
toSecondNumber:(NSNumber*)number2;
What is a promise
(OMPromise)?
It's just an object
It has state
— Unfulfilled
— Fulfilled
— Failed
It has other properties
— result
— error
— progress*
It has methods
- (OMPromise *)fulfilled:(void (^)(id result))fulfilHandler;
- (OMPromise *)failed:(void (^)(NSError *error))failHandler;
- (OMPromise *)progressed:(void (^)(float progress))progressHandler;
It lets you call handlers on a custom queue
- (OMPromise *)fulfilled:(void (^)(id result))fulfilHandler on:(dispatch_queue_t)queue;
- (OMPromise *)failed:(void (^)(NSError *error))failHandler on:(dispatch_queue_t)queue;
- (OMPromise *)progressed:(void (^)(float progress))progressHandler on:(dispatch_queue_t)queue;
So let's see it in action
OMPromise *sum = [calc addFirstNumber:@3 toSecondNumber:@2];
[[sum fulfilled:^(id result){
NSLog(@"Sum: %@", result);
}] failed:^(NSError *error){
NSLog(@"Failed to calculate because of: %@", error);
}];
Concatenation
sum1 = 3 + 2
sum2 = sum1 + 2
sum3 = sum1 + 3
Concatenation
OMPromise *sum1 = [calc addFirstNumber:@3 toSecondNumber:@2];
OMPromise *sum2 = [sum1 than:^(NSNumber *n1){
return [calc addFirstNumber:n1 toSecondNumber:@2];
}];
OMPromise *sum3 = [sum1 than:^(NSNumber *n1){
return [calc addFirstNumber:n1 toSecondNumber:@3];
}];
[[[OMPromise all:@[sum1, sum2, sum3]] fulfilled:^(NSArray *results){
NSLog(@"Sum1: %@, Sum2: %@, Sum3: %@", results[0], results[1], results[2]);
} on:dispatch_get_main_queue()] failed:^(NSError *error){
NSLog(@"Failed to calculate because of: %@", error);
} on:dispatch_get_main_queue()];
Deep chaining
OMPromise *sum = [OMPromise chain:[
^(NSNumber *n1){
return [calc addFirstNumber:@2 toSecondNumber:@3];
},
^(NSNumber *n1){
return [calc addFirstNumber:n1 toSecondNumber:@3];
},
^(NSNumber *n1){
return [calc addFirstNumber:n1 toSecondNumber:@2];
}
] initial:nil];
Recovering from failed promise
OMPromise *sum1 = [calc addFirstNumber:@3 toSecondNumber:@2];
OMPromise *rescuedSum = [sum1 rescue:^(NSError *error){
NSLog(@"Shit happens! %@", error);
return @0;
}];
Recap 1/3
@interface OMPromise : NSObject
@property(assign, readonly, nonatomic) OMPromiseState state;
@property(readonly, nonatomic) id result;
@property(readonly, nonatomic) NSError *error;
@property(assign, readonly, nonatomic) float progress;
Recap 2/3
- (OMPromise *)fulfilled:(void (^)(id result))fulfilHandler;
- (OMPromise *)failed:(void (^)(NSError *error))failHandler;
- (OMPromise *)progressed:(void (^)(float progress))progressHandler;
- (OMPromise *)then:(id (^)(id result))thenHandler;
- (OMPromise *)rescue:(id (^)(NSError *error))rescueHandler;
Recap 3/3
+ (OMPromise *)chain:(NSArray *)thenHandlers initial:(id)result;
+ (OMPromise *)any:(NSArray *)promises;
+ (OMPromise *)all:(NSArray *)promises;
Now what's about creating a
promise?
Convenience methods on OMPromise
+ (OMPromise *)promiseWithResult:(id)result;
+ (OMPromise *)promiseWithResult:(id)result after:(NSTimeInterval)delay;
+ (OMPromise *)promiseWithError:(NSError *)error;
+ (OMPromise *)promiseWithTask:(id (^)())task;
Example for success
- (OMPromise *)addFirstNumber:(NSNumber*)number1
toSecondNumber:(NSNumber*)number2
{
return [OMPromise promiseWithTask:^{
return @([n1 intValue] + [n2 intValue])
}];
}
Example for error
- (OMPromise *)addFirstNumber:(NSNumber*)number1
toSecondNumber:(NSNumber*)number2
{
return [OMPromise promiseWithTask:^{
return [NSError new];
}];
}
And what is the non convenient
way?
Defered object
@interface OMDeferred : OMPromise
+ (OMDeferred *)deferred;
- (OMPromise *)promise;
- (void)fulfil:(id)result;
- (void)fail:(NSError *)error;
- (void)progress:(float)progress;
@end
Example
- (OMPromise *)addFirstNumber:(NSNumber*)number1
toSecondNumber:(NSNumber*)number2
{
OMDeferred *deferred = [OMDeferred deferred];
dispatch_async(dispatch_get_global_queue(0, 0), ^
{
NSError *error = nil;
NSNumber *sum = [self addFirstNumber:number1
toSecondNumber:number2
withError:&error];
if(error){
[deferred fail:error];
} else {
[deferred fulfill:sum];
}
});
return deferred.promise;
}
Recap
— Promise is an immutable data structure
— Deferred is a subclass of Promise which makes it
mutable
To add up confusion I
implemented promises in
Swift
First attempt was just to
port it
Using Enums for state representation feels right
enum PromiseState{
case Unfulfilled
case Fulfilled
case Failed
}
I need inheritance so let's
use classes
Using constants as read only property is not a good
idea as you have to set them directly in the
initializer
class Promise<T> {
let state : PromiseState
let result : T
let error : NSError
}
Switching to var makes it work but Promise is not
immutable any more
class Promise<T> {
var state : PromiseState
var result : T?
var error : NSError?
init(){
state = .Unfulfilled
}
func fulfill(value : T){
result = value
state = .Fulfilled
}
}
Trying to make a read only property
class Promise<T> {
var state : PromiseState
var result : T? {
get {
return self.result
}
}
var error : NSError?
init(){
state = .Unfulfilled
}
}
Sadly this was an infinite loop
— There are no Read-Only properties in Swift
— Only Read-Only Computed Properties
Schock number 2
— There are no ivars in Swift
— And no visibility constrains (but they promised
to introduced those)
After initial shock pass, I
decided to go functional
func future<T>(execution : ()->FutureResult<T>)(handler: FutureHandler<T>) {
var result : FutureResult<T>?
var done = false
dispatch_async(dispatch_get_global_queue(0, 0)) {
result = execution()
done = true
}
dispatch_async(dispatch_get_global_queue(0, 0)) {
while !done {}
dispatch_async(dispatch_get_main_queue()) {
switch handler {
case .FulFilled(let fulfilled):
switch result! {
case .Result(let value):
fulfilled(value())
case .Error : println("can't process error")
}
case .Failed(let failed):
switch result! {
case .Result: println("can't process result")
case .Error(let error) : failed(error())
}
}
}
}
}
Main concept = currying
func add(n1:Int)(n2:Int) -> Int {
return n1 + n2
}
let increaseByTwo = add(2)
let sum = increaseByTwo(3)
Using enums with Associated Values
enum FutureHandler<T>{
case FulFilled((T)->())
case Failed((NSError)->())
}
enum FutureResult<T>{
case Result(@auto_closure ()->T)
case Error(@auto_closure ()->NSError)
}
Here is how you can use it:
var f = future {
FutureResult.Result(2 + 3)
}
f (handler: FutureHandler.FulFilled {
result in
println("Promise fulfilled : (result)")
})
It's not a beauty but it
works
However you can't concatenate futures in this
implementation
— typealias F = (FutureHandler<Int>) -> F
For the tough ones, who survived this talk:
— https://github.com/b52/OMPromises
— https://github.com/mzaks/siwft-future
Thank you!
Maxim Zaks (@iceX33)

Promise of an API

  • 1.
    Promise of anAPI Maxim Zaks (@iceX33) CocoaHeads Berlin
  • 2.
    Agenda — Consume apromise — Produce a promise — Swift
  • 3.
  • 4.
  • 5.
    - (NSNumber*)addFirstNumber:(NSNumber*)number1 toSecondNumber:(NSNumber*)number2; - (NSNumber*)addFirstNumber:(NSNumber*)number1 toSecondNumber:(NSNumber*)number2 withError:(NSError**)error; -(void)addFirstNumber:(NSNumber*)number1 toSecondNumber:(NSNumber*)number2 withBlock:(void (^)(NSNumber *sum, NSError *error))block;
  • 6.
    - (NSNumber*)addFirstNumber:(NSNumber*)number1 toSecondNumber:(NSNumber*)number2; - (NSNumber*)addFirstNumber:(NSNumber*)number1 toSecondNumber:(NSNumber*)number2 withError:(NSError**)error; -(void)addFirstNumber:(NSNumber*)number1 toSecondNumber:(NSNumber*)number2 withBlock:(void (^)(NSNumber *sum, NSError *error))block; - (void)addFirstNumber:(NSNumber*)number1 toSecondNumber:(NSNumber*)number2 success:(void (^)(NSNumber *sum))success failure:(void (^)(NSError *error))failur;
  • 7.
    And now witha real promise! - (OMPromise*)addFirstNumber:(NSNumber*)number1 toSecondNumber:(NSNumber*)number2;
  • 8.
    What is apromise (OMPromise)? It's just an object
  • 9.
    It has state —Unfulfilled — Fulfilled — Failed
  • 10.
    It has otherproperties — result — error — progress*
  • 11.
    It has methods -(OMPromise *)fulfilled:(void (^)(id result))fulfilHandler; - (OMPromise *)failed:(void (^)(NSError *error))failHandler; - (OMPromise *)progressed:(void (^)(float progress))progressHandler;
  • 12.
    It lets youcall handlers on a custom queue - (OMPromise *)fulfilled:(void (^)(id result))fulfilHandler on:(dispatch_queue_t)queue; - (OMPromise *)failed:(void (^)(NSError *error))failHandler on:(dispatch_queue_t)queue; - (OMPromise *)progressed:(void (^)(float progress))progressHandler on:(dispatch_queue_t)queue;
  • 13.
    So let's seeit in action
  • 14.
    OMPromise *sum =[calc addFirstNumber:@3 toSecondNumber:@2]; [[sum fulfilled:^(id result){ NSLog(@"Sum: %@", result); }] failed:^(NSError *error){ NSLog(@"Failed to calculate because of: %@", error); }];
  • 15.
    Concatenation sum1 = 3+ 2 sum2 = sum1 + 2 sum3 = sum1 + 3
  • 16.
    Concatenation OMPromise *sum1 =[calc addFirstNumber:@3 toSecondNumber:@2]; OMPromise *sum2 = [sum1 than:^(NSNumber *n1){ return [calc addFirstNumber:n1 toSecondNumber:@2]; }]; OMPromise *sum3 = [sum1 than:^(NSNumber *n1){ return [calc addFirstNumber:n1 toSecondNumber:@3]; }]; [[[OMPromise all:@[sum1, sum2, sum3]] fulfilled:^(NSArray *results){ NSLog(@"Sum1: %@, Sum2: %@, Sum3: %@", results[0], results[1], results[2]); } on:dispatch_get_main_queue()] failed:^(NSError *error){ NSLog(@"Failed to calculate because of: %@", error); } on:dispatch_get_main_queue()];
  • 18.
    Deep chaining OMPromise *sum= [OMPromise chain:[ ^(NSNumber *n1){ return [calc addFirstNumber:@2 toSecondNumber:@3]; }, ^(NSNumber *n1){ return [calc addFirstNumber:n1 toSecondNumber:@3]; }, ^(NSNumber *n1){ return [calc addFirstNumber:n1 toSecondNumber:@2]; } ] initial:nil];
  • 19.
    Recovering from failedpromise OMPromise *sum1 = [calc addFirstNumber:@3 toSecondNumber:@2]; OMPromise *rescuedSum = [sum1 rescue:^(NSError *error){ NSLog(@"Shit happens! %@", error); return @0; }];
  • 20.
    Recap 1/3 @interface OMPromise: NSObject @property(assign, readonly, nonatomic) OMPromiseState state; @property(readonly, nonatomic) id result; @property(readonly, nonatomic) NSError *error; @property(assign, readonly, nonatomic) float progress;
  • 21.
    Recap 2/3 - (OMPromise*)fulfilled:(void (^)(id result))fulfilHandler; - (OMPromise *)failed:(void (^)(NSError *error))failHandler; - (OMPromise *)progressed:(void (^)(float progress))progressHandler; - (OMPromise *)then:(id (^)(id result))thenHandler; - (OMPromise *)rescue:(id (^)(NSError *error))rescueHandler;
  • 22.
    Recap 3/3 + (OMPromise*)chain:(NSArray *)thenHandlers initial:(id)result; + (OMPromise *)any:(NSArray *)promises; + (OMPromise *)all:(NSArray *)promises;
  • 23.
    Now what's aboutcreating a promise?
  • 24.
    Convenience methods onOMPromise + (OMPromise *)promiseWithResult:(id)result; + (OMPromise *)promiseWithResult:(id)result after:(NSTimeInterval)delay; + (OMPromise *)promiseWithError:(NSError *)error; + (OMPromise *)promiseWithTask:(id (^)())task;
  • 25.
    Example for success -(OMPromise *)addFirstNumber:(NSNumber*)number1 toSecondNumber:(NSNumber*)number2 { return [OMPromise promiseWithTask:^{ return @([n1 intValue] + [n2 intValue]) }]; }
  • 26.
    Example for error -(OMPromise *)addFirstNumber:(NSNumber*)number1 toSecondNumber:(NSNumber*)number2 { return [OMPromise promiseWithTask:^{ return [NSError new]; }]; }
  • 27.
    And what isthe non convenient way?
  • 28.
    Defered object @interface OMDeferred: OMPromise + (OMDeferred *)deferred; - (OMPromise *)promise; - (void)fulfil:(id)result; - (void)fail:(NSError *)error; - (void)progress:(float)progress; @end
  • 29.
    Example - (OMPromise *)addFirstNumber:(NSNumber*)number1 toSecondNumber:(NSNumber*)number2 { OMDeferred*deferred = [OMDeferred deferred]; dispatch_async(dispatch_get_global_queue(0, 0), ^ { NSError *error = nil; NSNumber *sum = [self addFirstNumber:number1 toSecondNumber:number2 withError:&error]; if(error){ [deferred fail:error]; } else { [deferred fulfill:sum]; } }); return deferred.promise; }
  • 30.
    Recap — Promise isan immutable data structure — Deferred is a subclass of Promise which makes it mutable
  • 31.
    To add upconfusion I implemented promises in Swift
  • 32.
    First attempt wasjust to port it
  • 33.
    Using Enums forstate representation feels right enum PromiseState{ case Unfulfilled case Fulfilled case Failed }
  • 34.
    I need inheritanceso let's use classes
  • 35.
    Using constants asread only property is not a good idea as you have to set them directly in the initializer class Promise<T> { let state : PromiseState let result : T let error : NSError }
  • 36.
    Switching to varmakes it work but Promise is not immutable any more class Promise<T> { var state : PromiseState var result : T? var error : NSError? init(){ state = .Unfulfilled } func fulfill(value : T){ result = value state = .Fulfilled } }
  • 37.
    Trying to makea read only property class Promise<T> { var state : PromiseState var result : T? { get { return self.result } } var error : NSError? init(){ state = .Unfulfilled } }
  • 38.
    Sadly this wasan infinite loop — There are no Read-Only properties in Swift — Only Read-Only Computed Properties
  • 39.
    Schock number 2 —There are no ivars in Swift — And no visibility constrains (but they promised to introduced those)
  • 40.
    After initial shockpass, I decided to go functional
  • 41.
    func future<T>(execution :()->FutureResult<T>)(handler: FutureHandler<T>) { var result : FutureResult<T>? var done = false dispatch_async(dispatch_get_global_queue(0, 0)) { result = execution() done = true } dispatch_async(dispatch_get_global_queue(0, 0)) { while !done {} dispatch_async(dispatch_get_main_queue()) { switch handler { case .FulFilled(let fulfilled): switch result! { case .Result(let value): fulfilled(value()) case .Error : println("can't process error") } case .Failed(let failed): switch result! { case .Result: println("can't process result") case .Error(let error) : failed(error()) } } } } }
  • 42.
    Main concept =currying func add(n1:Int)(n2:Int) -> Int { return n1 + n2 } let increaseByTwo = add(2) let sum = increaseByTwo(3)
  • 43.
    Using enums withAssociated Values enum FutureHandler<T>{ case FulFilled((T)->()) case Failed((NSError)->()) } enum FutureResult<T>{ case Result(@auto_closure ()->T) case Error(@auto_closure ()->NSError) }
  • 44.
    Here is howyou can use it: var f = future { FutureResult.Result(2 + 3) } f (handler: FutureHandler.FulFilled { result in println("Promise fulfilled : (result)") })
  • 45.
    It's not abeauty but it works
  • 46.
    However you can'tconcatenate futures in this implementation — typealias F = (FutureHandler<Int>) -> F
  • 47.
    For the toughones, who survived this talk: — https://github.com/b52/OMPromises — https://github.com/mzaks/siwft-future
  • 48.