Wednesday, March 25, 2015

Using ReactiveCocoa from Swift

We're trying out swift at work, and have an existing app which makes quite a bit of use of ReactiveCocoa.
Looking online, I found MVVM, Swift and ReactiveCocoa - It's all good! from Colin Eberhardt. It's a good intro, but I found I had trouble when trying to use the tricker subscribe overloads.

Without further ado, here's my own take on the extensions.
Licensing wise I declare it to be public domain, do with it as you will.
(Note: Implemented on Xcode 6.2 with Swift 1.1)


import Foundation

extension RACSignal {

    // Subscribe with an on-next block
    // Equivalent of - (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock {
    func subscribe(next nextClosure:(T)->Void) -> RACDisposable {
        return self.subscribeNext { (x: AnyObject!) -> () in nextClosure(x as T) }
    }
    
    // Subscribe with an on-next and on-completed block
    // Equivalent of - (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock completed:(void (^)(void))completedBlock;
    func subscribe(next nextClosure:(T)->Void, completed completedClosure:()->Void) -> RACDisposable {
        return self.subscribeNext(
            { (x: AnyObject!) -> () in nextClosure(x as T) },
            completed:{ () in completedClosure() })
    }
    
    // Subscribe with an on-next and on-error block
    // Equivalent of - (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock error:(void (^)(NSError *error))errorBlock {
    func subscribe(next nextClosure:(T)->Void, error errorClosure:(NSError)->Void) -> RACDisposable {
        return self.subscribeNext(
            { (x: AnyObject!) -> () in nextClosure(x as T) },
            error:{ (x:NSError!) in errorClosure(x) })
    }
    
    // Subscribe with an on-next, on-error and on-completed block
    // Equivalent of - (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock error:(void (^)(NSError *error))errorBlock completed:(void (^)(void))completedBlock;
    func subscribe(next nextClosure:(T)->Void, error errorClosure:(NSError)->Void, completed completedClosure:()->Void) -> RACDisposable {
        return self.subscribeNext(
            { (x: AnyObject!) -> () in nextClosure(x as T) },
            error:{ (x:NSError!) in errorClosure(x) },
            completed:{ () in completedClosure() })
    }
    
    // Subscribe with an on-error block
    // Equivalent of - (RACDisposable *)subscribeError:(void (^)(NSError *error))errorBlock;
    func subscribe(error errorClosure:(NSError)->Void) -> RACDisposable {
        return self.subscribeError { (x: NSError!) -> () in errorClosure(x) }
    }
    
    // Subscribe with an on-error and on-completed block
    // Equivalent of - (RACDisposable *)subscribeError:(void (^)(NSError *error))errorBlock completed:(void (^)(void))completedBlock;
    func subscribe(error errorClosure:(NSError)->Void, completed completedClosure:()->Void) -> RACDisposable {
        return self.subscribeError(
            { (x: NSError!) -> () in errorClosure(x) },
            completed:{ () in completedClosure() })
    }
}

Now, how to use it (this can be a bit unclear sometimes in swift)

To subscribe for on-next notifications


someSignal.subscribe { (arg:Type) in .... }

e.g. with an RACSignal publishing strings it would be
someSignal.subscribe { (x:String) in doSomething(x) }
or
someSignal.subscribe(
    next:{ (x:String) in 
        doSomething(x) 
    })

To subscribe for on-error notifications


someSignal.subscribe(error:{ (e) in .... })
or
someSignal.subscribe(
    error:{ (e) in 
        logError(e) 
    })


e.g. with an RACSignal publishing strings it would be
someSignal.subscribe(error:{ (e) in logError(e) }

You don't need to specify (error:NSError) as the compiler is smart enough to infer that, however you do need to specify the explicit error: parameter name, so the compiler knows this is an error handler, not an on-next handler

To subscribe for on-next and on-completed notifications


someSignal.subscribe({ (arg:Type) in .... }, completed:{ ... })

e.g. with an RACSignal publishing strings it would be
someSignal.subscribe({ (x:String) in doSomething(x) }, completed:{ allDone() })
or
someSignal.subscribe(
    next:{ (x:String) in 
        doSomething(x) 
    }, 
    completed:{ 
       allDone() 
    })


Warning If the code in your completed block (e.g. the allDone function) doesn't return void, the compiler will infer it as the return type of the completed block, and you'll get a confusing-looking Extra argument 'completed' in call error. The fix is to insert an explicit return, e.g.

someSignal.subscribe(
    next:{ (x:String) in 
        doSomething(x) 
    }, 
    completed:{ 
       allDone() 
       return
    })


To subscribe for on-next and on-error notifications


someSignal.subscribe({ (arg:Type) in .... }, error:{ (e) in ... })

e.g. with an RACSignal publishing strings it would be
someSignal.subscribe({ (x:String) in doSomething(x) }, error:{ (e) in logError(e) })
or
someSignal.subscribe(
    next:{ (x:String) in 
        doSomething(x) 
    }, 
    error:{ (e) in
       logError(e) 
    })


To subscribe for all 3 - on-next, on-error and on-completed notifications


someSignal.subscribe({ (arg:Type) in .... }, error:{ (e) in ... }, completed:{ ... })

e.g. with an RACSignal publishing strings it would be
someSignal.subscribe({ (x:String) in doSomething(x) }, error:{ (e) in logError(e) }, completed:{ allDone() })
or
someSignal.subscribe(
    next:{ (x:String) in 
        doSomething(x) 
    }, 
    error:{ (e) in
       logError(e) 
    },
    completed:{
        allDone()
    })


The warning above (sometimes you might need an explicit return in the completed block) applies here too.

To subscribe for on-error and on-completed notifications


someSignal.subscribe(error:{ (e) in ... }, completed:{ ... })

e.g. with an RACSignal publishing strings it would be
someSignal.subscribe({ error:{ (e) in logError(e) }, completed:{ allDone() })
or
someSignal.subscribe(
    error:{ (e) in
       logError(e) 
    },
    completed: {
        allDone()
    })


The warning above (sometimes you might need an explicit return in the completed block) applies here too.

Wrap-up

I hope this helps!

P.S. - if you're wondering where the "on-completed only" version is - there isn't one, because ReactiveCocoa doesn't seem to provide one for some reason. If you want that behaviour, you can subscribe with an empty on-next or on-error callback.