reactivecocoa and swift, better together

66
ReactiveCocoa and Swift Better Together @ColinEberhardt ShinobiControls

Upload: colin-eberhardt

Post on 01-Dec-2014

688 views

Category:

Technology


3 download

DESCRIPTION

ReactiveCocoa is an elegant framework that radically changes the way we structure our applications and handle flows of data. However, it's beauty is somewhat marred by Objective-C! In this talk Colin will cover the basics of ReactiveCocoa and the principles of Functional Reactive Programming. Through simple practical examples he will show how ReactiveCocoa and Swift form a beautiful partnership.

TRANSCRIPT

Page 1: ReactiveCocoa and Swift, Better Together

ReactiveCocoa and Swift Better Together

@ColinEberhardt ShinobiControls

Page 2: ReactiveCocoa and Swift, Better Together

www.shinobicontrols.com

Page 3: ReactiveCocoa and Swift, Better Together
Page 4: ReactiveCocoa and Swift, Better Together

What is ReactiveCocoa?

Page 5: ReactiveCocoa and Swift, Better Together
Page 6: ReactiveCocoa and Swift, Better Together

Functional Reactive Programming

Functional Programming

Reactive Programming+

=

Page 7: ReactiveCocoa and Swift, Better Together

ReactiveCocoa In my own words

Page 8: ReactiveCocoa and Swift, Better Together

Every line of code we write is executed in reaction to an event

Page 9: ReactiveCocoa and Swift, Better Together

But … these events come in many different forms

KVO

NSNotification

delegatestarget-action

callbacks

Page 10: ReactiveCocoa and Swift, Better Together

ReactiveCocoa provides a common interface for all events!

Page 11: ReactiveCocoa and Swift, Better Together

… this allows us to define a language for manipulating,

transforming and coordinating events

Page 12: ReactiveCocoa and Swift, Better Together

ReactiveCocoa Made Simple

Page 13: ReactiveCocoa and Swift, Better Together

Get Reactive

let textSignal: RACSignal = usernameTextField.rac_textSignal()

textSignal.subscribeNext { (text: AnyObject!) -> Void in let textString = text as String println(textString) }

Page 14: ReactiveCocoa and Swift, Better Together

Get Reactive

Page 15: ReactiveCocoa and Swift, Better Together

Objective-C Friction

let textSignal: RACSignal = usernameTextField.rac_textSignal()

textSignal.subscribeNext { (text: AnyObject!) -> Void in let textString = text as String println(textString) }

Page 16: ReactiveCocoa and Swift, Better Together

Simplified with Swift

textSignal.subscribeNextAs { (text: String) -> () in println(text) }

func subscribeNextAs<T>(nextClosure:(T) -> ()) -> () { self.subscribeNext { (next: AnyObject!) -> () in let nextAsT = next! as T nextClosure(nextAsT) } }

textSignal.subscribeNext { (text: AnyObject!) -> Void in let textString = text as String println(textString) }

Page 17: ReactiveCocoa and Swift, Better Together

Signals

• A signal emits events

• next

• error

• completed

• A signal can have none, one or more subscribers

textSignal.subscribeNextAs({ (text: String) in println(text) }, error: { (error) in // ... }, completed: { // ... })

Page 18: ReactiveCocoa and Swift, Better Together

Events

Signals can emit none, one or more next events, optionally followed by either an error or completed

NEXT NEXT NEXT NEXT …

NEXT NEXT ERROR

COMPLETEDintervals do not have to be regular!

Page 19: ReactiveCocoa and Swift, Better Together

Signal all things

• Network request

• A single next, followed by a completed

• Large download

• Multiple next events, representing partial data, followed by completed

• UI control

• An infinite stream of next events

Page 20: ReactiveCocoa and Swift, Better Together

filter

A filter is a ‘gate’, filtering-out events which do not match the given condition

let textSignal: RACSignal = usernameTextField.rac_textSignal()

let filteredText = textSignal.filterAs { (text: NSString) -> Bool in return text.length > 3 }

filteredText.subscribeNextAs { (text: String) in println(text) }

Page 21: ReactiveCocoa and Swift, Better Together
Page 22: ReactiveCocoa and Swift, Better Together

What exactly are events?

• What does a next event actually look like?

• Anything!

• Signals are an interface for handling asynchronous events

• The event contents are context dependant

Page 23: ReactiveCocoa and Swift, Better Together

map

Transforms each next event (please ignore the types!)

let textSignal: RACSignal = usernameTextField.rac_textSignal()

let textLength = textSignal.mapAs { (text: NSString) -> NSNumber in return text.length }

textLength.subscribeNextAs { (length: NSNumber) in println(length) }

Page 24: ReactiveCocoa and Swift, Better Together
Page 25: ReactiveCocoa and Swift, Better Together

Creating a pipelinelet textSignal: RACSignal = usernameTextField.rac_textSignal()

let textLength = textSignal.mapAs { (text: NSString) -> NSNumber in return text.length }

let filteredText = textLength.filterAs { (number: NSNumber) -> Bool in return number > 3 }

filteredText.subscribeNextAs { (length: NSNumber) in println(length) }

Page 26: ReactiveCocoa and Swift, Better Together

Fluent syntax

usernameTextField .rac_textSignal() .mapAs { (text: NSString) -> NSNumber in return text.length } .filterAs { (number: NSNumber) -> Bool in return number > 3 } .subscribeNextAs { (length: NSNumber) in println(length) }

Page 27: ReactiveCocoa and Swift, Better Together

rac_textSignal- map- filter- subscribeNext-string- number- number-

PUSH%

ReactiveCocoa Signals

Lazy Sequences

lazy% map% filter% generator%string% number% number%

PULL$

Page 28: ReactiveCocoa and Swift, Better Together

TweetSentiment

Page 29: ReactiveCocoa and Swift, Better Together
Page 30: ReactiveCocoa and Swift, Better Together

searchTextField .rac_textSignal() .mapAs { (text: NSString) -> UIColor in text.length <= 3 ? UIColor.lightRedColor() : UIColor.whiteColor() } .setKeyPath("backgroundColor", onObject: searchTextField)

Page 31: ReactiveCocoa and Swift, Better Together

Don’t search for very short search terms

Page 32: ReactiveCocoa and Swift, Better Together

Don’t issue a new query on each key press

Page 33: ReactiveCocoa and Swift, Better Together

searchTextField .rac_textSignal() .throttle(0.5) .filterAs { (text: NSString) -> Bool in text.length > 3 } .subscribeNextAs { (text: NSString) -> () in println(text) }

Page 34: ReactiveCocoa and Swift, Better Together

rac_textSignal-

filter- setKeyPath-

thro5le- filter- subscribeNext-

Page 35: ReactiveCocoa and Swift, Better Together

Creating Signals

Page 36: ReactiveCocoa and Swift, Better Together

private func signalForSearchWithText(text: String) -> RACSignal {

func requestforSearchText(text: String) -> SLRequest { return ...

} return RACSignal.createSignal { subscriber -> RACDisposable! in let request = requestforSearchText(text) let maybeTwitterAccount = self.getTwitterAccount() if let twitterAccount = maybeTwitterAccount { request.account = twitterAccount request.performRequestWithHandler { (data, response, _) -> Void in if response != nil && response.statusCode == 200 { let timelineData = NSJSONSerialization.parseJSONToDictionary(data) subscriber.sendNext(timelineData) subscriber.sendCompleted() } else { subscriber.sendError(TwitterInstantError.InvalidResponse.toError()) } } } else { subscriber.sendError(TwitterInstantError.NoTwitterAccounts.toError()) } return nil } }

Page 37: ReactiveCocoa and Swift, Better Together

searchTextField .rac_textSignal() .throttle(0.5) .filterAs { (text: NSString) -> Bool in text.length > 3 } .mapAs { (text: NSString) -> RACStream in self.signalForSearchWithText(text) } .subscribeNextAs { ... }

Page 38: ReactiveCocoa and Swift, Better Together

searchTextField .rac_textSignal() .throttle(0.5) .filterAs { (text: NSString) -> Bool in text.length > 3 } .flattenMapAs { (text: NSString) -> RACStream in self.signalForSearchWithText(text) } .subscribeNextAs { ... }

Page 39: ReactiveCocoa and Swift, Better Together

searchTextField .rac_textSignal() .throttle(0.5) .filterAs { (text: NSString) -> Bool in text.length > 3 } .flattenMapAs { (text: NSString) -> RACStream in self.signalForSearchWithText(text) } .deliverOn(RACScheduler.mainThreadScheduler()) .subscribeNextAs { (tweets: NSDictionary) in let statuses = tweets["statuses"] as [NSDictionary] self.tweets = statuses.map { Tweet(json: $0) } self.tweetsTableView.reloadData() self.tweetsTableView.scrollToTop() }

Page 40: ReactiveCocoa and Swift, Better Together

rac_textSignal-

filter- setKeyPath-

thro5le- filter- fla5enMap-

twi5erSearch-

deliverOn- subscribeNext-

Page 41: ReactiveCocoa and Swift, Better Together

requestAccessToTwitterSignal() .then { self.searchTextField.rac_textSignal() } .filterAs { ... } .throttle(0.5) ...

Page 42: ReactiveCocoa and Swift, Better Together

rac_textSignal-

filter- setKeyPath-

thro5le- filter- fla5enMap-

twi5erSearch-

deliverOn- subscribeNext-

requestAccess-signal- then-

Page 43: ReactiveCocoa and Swift, Better Together

Error Handling

Page 44: ReactiveCocoa and Swift, Better Together

requestAccessToTwitterSignal() .then { self.searchTextField.rac_textSignal() } .filterAs { ... } .throttle(0.5) .doNext { ... } .flattenMapAs { ... self.signalForSearchWithText(text) } .deliverOn(RACScheduler.mainThreadScheduler()) .subscribeNextAs({ ... }, { (error) in println(error) })

Page 45: ReactiveCocoa and Swift, Better Together

Fetching Sentiment Data

Page 46: ReactiveCocoa and Swift, Better Together

Fire a sentiment API request for each tweet and merge the signals?

Page 47: ReactiveCocoa and Swift, Better Together

Fetch sentiment data when cells become visible?

Page 48: ReactiveCocoa and Swift, Better Together

Fetch sentiment data when a cell has been visible for a short duration?

Page 49: ReactiveCocoa and Swift, Better Together

Signal All Things!

Page 50: ReactiveCocoa and Swift, Better Together

RACSignal .interval(0.5, onScheduler: RACScheduler(priority: RACSchedulerPriorityBackground)) .take(1) .takeUntil(rac_prepareForReuseSignal) .flattenMap { (next) -> RACStream in self.obtainSentimentSignal(hasTweet) } .deliverOn(RACScheduler.mainThreadScheduler()) .subscribeNextAs { (sentiment: String) in NSNotificationCenter.defaultCenter() .postNotificationName("sentiment", object: sentiment) self.sentimentIndicator.backgroundColor = self.sentimentToColor(sentiment) }

Page 51: ReactiveCocoa and Swift, Better Together
Page 52: ReactiveCocoa and Swift, Better Together

I curried a function!

Page 53: ReactiveCocoa and Swift, Better Together
Page 54: ReactiveCocoa and Swift, Better Together

func scale(domainMin: Double, domainMax: Double, screenMin: Double, screenMax: Double, pt: Double) -> CGFloat {

let value = ((pt - domainMin) / (domainMax - domainMin)) * (screenMax - screenMin) + screenMin return CGFloat(value) }

Page 55: ReactiveCocoa and Swift, Better Together

func scale(domainMin: Double, domainMax: Double, screenMin: Double, screenMax: Double)(pt: Double) -> CGFloat {

let value = ((pt - domainMin) / (domainMax - domainMin)) * (screenMax - screenMin) + screenMin return CGFloat(value) }

Page 56: ReactiveCocoa and Swift, Better Together

let xscale = scale(0, Double(maxValue), 35, Double(self.bounds.width - 35)) let yscale = scale(0, 3, 0, Double(self.bounds.height))

positiveLayer.frame = CGRect(x: xscale(pt: 0), y: yscale(pt: 1), width: xdelta(pt1: Double(positive), pt2: 0.0), height: yscale(pt: 1)) neutralLayer.frame = ...

func delta(scale:(Double) -> CGFloat)(pt1: Double, pt2: Double) -> CGFloat { return scale(pt1) - scale(pt2) }

let xdelta = delta(xscale)

Page 57: ReactiveCocoa and Swift, Better Together
Page 58: ReactiveCocoa and Swift, Better Together

Better Together

Page 59: ReactiveCocoa and Swift, Better Together

Swift loves Fluent APIs

Page 60: ReactiveCocoa and Swift, Better Together

requestAccessToTwitterSignal() .then { self.searchTextField.rac_textSignal() } .filterAs { (text: NSString) -> Bool in text.length > 3 } .doNext { (any) in self.tweetsTableView.alpha = 0.5 } .throttle(0.5) .doNext { (any) in NSNotificationCenter.defaultCenter().postNotificationName("sentiment", object: "reset") } .flattenMapAs { (text: NSString) -> RACStream in self.signalForSearchWithText(text) } .deliverOn(RACScheduler.mainThreadScheduler()) .subscribeNextAs({ (tweets: NSDictionary) in let statuses = tweets["statuses"] as [NSDictionary] self.tweets = statuses.map { Tweet(json: $0) } self.tweetsTableView.reloadData() self.tweetsTableView.scrollToTop() self.tweetsTableView.alpha = 1.0 }, { (error) in println(error) })

Page 61: ReactiveCocoa and Swift, Better Together

http://stackoverflow.com/questions/3124001/fluent-interface-pattern-in-objective-c

Page 62: ReactiveCocoa and Swift, Better Together

http://stackoverflow.com/questions/3124001/fluent-interface-pattern-in-objective-c

Page 63: ReactiveCocoa and Swift, Better Together

[[[[[[[[self requestAccessToTwitterSignal] then:^RACSignal *{ @strongify(self) return self.searchText.rac_textSignal; }] filter:^BOOL(NSString *text) { @strongify(self) return [self isValidSearchText:text]; }] throttle:0.5] flattenMap:^RACStream *(NSString *text) { @strongify(self) return [self signalForSearchWithText:text]; }] map:^id(NSDictionary *jsonSearchResult) { NSArray *statuses = jsonSearchResult[@"statuses"]; NSArray *tweets = [statuses linq_select:^id(id tweet) { return [RWTweet tweetWithStatus:tweet]; }]; return tweets; }] deliverOn:[RACScheduler mainThreadScheduler]] subscribeNext:^(NSArray *tweets) { [self.resultsViewController displayTweets:tweets]; } error:^(NSError *error) { NSLog(@"An error occurred: %@", error); }];

Page 64: ReactiveCocoa and Swift, Better Together

ReactiveCocoa hates state

Page 65: ReactiveCocoa and Swift, Better Together

39 constants12 variables

6 outlets (not my fault!)

1 UIWindow5 UI state variables

Page 66: ReactiveCocoa and Swift, Better Together

ReactiveCocoa and Swift Better Together

@ColinEberhardt ShinobiControls

TwitterSentiment:https://github.com/ColinEberhardt/ReactiveSwiftLondon

MVVM with ReactiveCocoa and Swift:https://github.com/ColinEberhardt/ReactiveSwiftFlickrSearch

Tutorials:http://www.raywenderlich.com/u/ColinEberhardt