Taking a peek at ReactiveCocoa 3.0

Taking a peek at ReactiveCocoa 3.0

Cocoaheads Montréal - 14/07/2015

B4f9306896b6eaa56a9a6b9048285f53?s=128

Romain Pouclet

July 14, 2015
Tweet

Transcript

  1. Taking a peak at ReactiveCocoa 3.0 Cocoaheads Montreal 14/07/2015

  2. My twitter avatar shows me casually playing guitar. I got

    nice comments when I also used it on Facebook.
  3. I was actually warming up with a super-simple exercice. (Don't

    tell my mom, she was super-proud)
  4. I started taking lessons a few months ago, I have

    a great teacher but to be honest, I'm still not very good at it. !
  5. This is pretty much the same with ReactiveCocoa.

  6. Me, Myself and ReactiveCocoa • I have been using it

    for a couple of years, on and off • I'm no expert, but I love the tech, the idea and the community • Ash Furrow said it was okay
  7. ReactiveCocoa, a history • OpenSourced by the fancy people at

    Github in 2012 • Used in Github for Mac, for example • A new version written in swift released (as a beta) a few month ago • (The history is not that important)
  8. What is ReactiveCocoa ?

  9. ReactiveCocoa is a framework used for composing and transforming sequences

    of values
  10. No seriously, what is it?

  11. I like to see ReactiveCocoa as a tool to send

    stuff through pipes
  12. Value → Different Value

  13. Sequence of Values → Different Values

  14. In ReactiveCocoa, you play with Signals - (RACSignal *)callWebservice {

    return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { NSURLSessionDataTask *task = [self.session dataTaskWithURL: url completionHandler:^(...) { // ... }]; [task resume]; return [RACDisposable disposableWithBlock:^{ [task cancel]; }]; }]; }
  15. In ReactiveCocoa, you play with Signals NSArray *payload = [NSJSONSerialization

    JSONObjectWithData: data options: 0 error: nil]; // Some error handling would be nice ! [payload enumerateObjectsUsingBlock:^(NSDictionary *obj, NSUInteger idx, BOOL *stop) { [subscriber sendNext: obj]; }]; [subscriber sendCompleted];
  16. In ReactiveCocoa, you play with Signals [[client callWebservice] map:^(NSDictionary *payload)

    { return [[FancyObject alloc] initWithDictionary: payload]; }];
  17. ReactiveCocoa 2.0 fitted really well with Objective-C

  18. But things can rapidly get out of hands [[[[client logInUser]

    flattenMap:^(User *user) { return [client loadCachedMessagesForUser:user]; }] flattenMap:^(NSArray *messages) { return [client fetchMessagesAfterMessage:messages.lastObject]; }] subscribeNext:^(NSArray *newMessages) { NSLog(@"New messages: %@", newMessages); } completed:^{ NSLog(@"Fetched all messages."); }];
  19. [[[[[[[[[[[ !!!!!!!!

  20. ReactiveCocoa and Swift • Rewrote from scratch to benefit from

    Swift awesomeness • Strong typing everywhere • Still in beta, use with caution
  21. Getting started with ReactiveCocoa 3.0 • Drop the framework available

    on Github directly in your project • Use Cocoapods • Use Carthage, which uses ReactiveCocoa internally :mindblown.gif:
  22. Simple example func tick() -> Signal<String, NoError> { var count

    = 0 return Signal { sink in NSTimer.schedule(repeatInterval: 1.0) { timer in sendNext(sink, "tick #\(count++)") } return nil } }
  23. Simple example: observing the signal let signal = tick() signal.observe(next:

    { println($0) })
  24. Simple example func fetchCharacterNames() -> SignalProducer<String, NoError> { return SignalProducer

    { observable, disposable in let names = ["Buffy", "Angel", "Spike", "Xander"] for (_, name) in enumerate(names) { sendNext(observable, name) } sendCompleted(observable) } }
  25. Simple example: observing the signal let signal = fetchCharacterNames() signal.observe(next:

    { println($0) })
  26. Transforming values: what you'd expect createSignalProducer().map({ name in return Character(name:

    name) })
  27. WRONG! ReactiveCocoa 3.0 is a lot more "functional" and map

    is actually a function that returns a new signal public func map<T, U, E>(transform: T -> U) -> Signal<T, E> -> Signal<U, E> { // ... }
  28. If this code makes you feel stupid, join the club

    and we'll all get matching tatoos
  29. Transforming values: using map let charactersSignal = createSignalProducer() let mappedSignal

    = map(charactersSignal, { Character(name: $0) }) mappedSignal.start(...)
  30. Invoking the power of the |> operator createSignalProducer() |> map({

    Character(name: $0) }) • Coming from the F# language • Makes your code pretty • Confuses Xcode like nothing before
  31. More complex (and relevant) example: the Photos framework • Working

    on an application that uses the Photos framework • Retrieving the latest photos = callbacksoup
  32. More complex example: the Photos framework 1. Iterating over a

    list of albums 2. For each of these albums, request the assets 3. For each asset, request a UIImage (asynchronously)
  33. Step 1: Fetching albums func fetchCollections() -> SignalProducer<PHAssetCollection, NoError> {

    return SignalProducer { observer, disposable in let fetchOptions = PHFetchOptions() let collections = PHAssetCollection.fetchAssetCollectionsWithType(.SmartAlbum, subtype: .Any, options: fetchOptions) for index in 0..<collections.count { let collection = collections.objectAtIndex(index) as! PHAssetCollection sendNext(observer, collection) } sendCompleted(observer) } }
  34. Step 1: Fetching collections fetchCollections().start(next: { collection in println(“Collection =

    \(collection.localizedTitle)”) })
  35. Step 2: Fetching assets in specific collection func fetchAssets(inCollection collection:

    PHAssetCollection) -> SignalProducer<PHAsset, NoError> { return SignalProducer { observer, disposable in // We only want the images let assetsOptions = PHFetchOptions() assetsOptions.predicate = NSPredicate(format: “mediaType = %i”, PHAssetMediaType.Image.rawValue) let assets = PHAsset.fetchAssetsInAssetCollection(collection, options: assetsOptions) for assetIndex in 0..<assets.count { sendNext(observer, assets.objectAtIndex(assetIndex) as! PHAsset) } sendCompleted(observer) } }
  36. Step 3: Fetching thumbnails func fetchImage(forAsset asset: PHAsset) -> SignalProducer<UIImage,

    NoError> { return SignalProducer { observer, disposable in let size = CGSize(width: asset.pixelWidth, height: asset.pixelHeight) PHImageManager.defaultManager().requestImageForAsset(asset, targetSize: size, contentMode: .AspectFit, options: PHImageRequestOptions(), resultHandler: { (image, infos) -> Void in sendNext(observer, image) sendCompleted(observer) }) } }
  37. Step 4: Merging all the things func fetchLatestScreenshot() -> SignalProducer<UIImage,

    NoError> { return fetchCollections() |> flatMap(.Merge, { collection in return fetchAssets(inCollection: collection) |> flatMap(.Latest, { asset in return fetchImage(forAsset: asset) }) }) }
  38. flatMap is a common concept in functional programing • Apply

    a function that returns a sequence for each element in the "pipe" • Here it applies a function to every collection and returns a sequence of assets
  39. Bonus: Filtering assets // ... fetchAssets(inCollection: collection) |> filter({ asset

    in let size = CGSize(width: asset.pixelWidth, height: asset.pixelHeight) return find(resolutions, size) != nil }) // ...
  40. Conclusion • ReactiveCocoa is a great tool, even if it

    sounds confusing your app can benefit from it • Xcode will be confused a lot, it makes the development suspensefull • There is a lot I haven't covered (observing properties...) • Questions?
  41. Thanks! 1. Picture by the awesome @Misszonzonnette 2. Simple tick

    example Shamelessly borrowed from http:// blog.scottlogic.com 3. We're hiring at Mirego!