SlideShare a Scribd company logo
ReactiveCocoa and Swift 
Better Together 
@ColinEberhardt 
ShinobiControls
www.shinobicontrols.com
ReactiveCocoa and Swift, Better Together
What is 
ReactiveCocoa?
ReactiveCocoa and Swift, Better Together
Functional Programming 
+ 
Reactive Programming 
= 
Functional Reactive 
Programming
ReactiveCocoa 
In my own words
Every line of code we write is 
executed in reaction to an event
But … these events come in 
many different forms 
KVO 
delegates 
NSNotification 
target-action 
callbacks
ReactiveCocoa provides a 
common interface for all events!
… this allows us to define a 
language for manipulating, 
transforming and coordinating 
events
ReactiveCocoa 
Made Simple
Get Reactive 
let textSignal: RACSignal = 
usernameTextField.rac_textSignal() 
textSignal.subscribeNext { 
(text: AnyObject!) -> Void in 
let textString = text as String 
println(textString) 
}
Get Reactive
Objective-C Friction 
let textSignal: RACSignal = 
usernameTextField.rac_textSignal() 
textSignal.subscribeNext { 
(text: AnyObject!) -> Void in 
let textString = text as String 
println(textString) 
}
Simplified with Swift 
func subscribeNextAs<T>(nextClosure:(T) -> ()) -> () { 
self.subscribeNext { 
textSignal.subscribeNextAs { 
(text: String) -> () in 
println(text) 
} 
(next: AnyObject!) -> () in 
let nextAsT = next! as T 
nextClosure(nextAsT) 
} 
} 
textSignal.subscribeNext { 
(text: AnyObject!) -> Void in 
let textString = text as String 
println(textString) 
}
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: { 
// ... 
})
Events 
Signals can emit none, one or more next events, 
optionally followed by either an error or 
completed 
COMPLETED 
NEXT NEXT ERROR 
NEXT NEXT NEXT NEXT … 
intervals do not 
have to be 
regular!
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
filter 
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) 
} 
A filter is a ‘gate’, filtering-out events which do not match 
the given condition
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
map 
let textSignal: RACSignal = usernameTextField.rac_textSignal() 
let textLength = textSignal.mapAs { 
(text: NSString) -> NSNumber in 
return text.length 
} 
textLength.subscribeNextAs { 
(length: NSNumber) in 
println(length) 
} 
Transforms each next event (please ignore the types!)
ReactiveCocoa and Swift, Better Together
Creating a pipeline 
let 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) 
}
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) 
}
number- number- 
ReactiveCocoa Signals 
rac_textSignal- map- filter- subscribeNext-string- 
PUSH% 
Lazy Sequences 
string% number% number% 
lazy% map% filter% generator% 
PULL$
TweetSentiment
ReactiveCocoa and Swift, Better Together
searchTextField 
.rac_textSignal() 
.mapAs { 
(text: NSString) -> UIColor in 
text.length <= 3 
? UIColor.lightRedColor() 
: UIColor.whiteColor() 
} 
.setKeyPath("backgroundColor", onObject: searchTextField)
Don’t search for very short 
search terms
Don’t issue a new query on 
each key press
searchTextField 
.rac_textSignal() 
.throttle(0.5) 
.filterAs { 
(text: NSString) -> Bool in 
text.length > 3 
} 
.subscribeNextAs { 
(text: NSString) -> () in 
println(text) 
}
rac_textSignal-filter- 
setKeyPath-thro5le- 
filter- subscribeNext-
Creating 
Signals
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 
} 
}
searchTextField 
.rac_textSignal() 
.throttle(0.5) 
.filterAs { 
(text: NSString) -> Bool in 
text.length > 3 
} 
.mapAs { 
(text: NSString) -> RACStream in 
self.signalForSearchWithText(text) 
} 
.subscribeNextAs { 
... 
}
searchTextField 
.rac_textSignal() 
.throttle(0.5) 
.filterAs { 
(text: NSString) -> Bool in 
text.length > 3 
} 
.flattenMapAs { 
(text: NSString) -> RACStream in 
self.signalForSearchWithText(text) 
} 
.subscribeNextAs { 
... 
}
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() 
}
rac_textSignal-filter- 
setKeyPath-thro5le- 
filter- fla5enMap-twi5erSearch-deliverOn- 
subscribeNext-
requestAccessToTwitterSignal() 
.then { 
self.searchTextField.rac_textSignal() 
} 
.filterAs { 
... 
} 
.throttle(0.5) 
...
rac_textSignal-filter- 
setKeyPath-thro5le- 
filter- fla5enMap-twi5erSearch-deliverOn- 
subscribeNext-requestAccess-signal- 
then-
Error 
Handling
requestAccessToTwitterSignal() 
.then { 
self.searchTextField.rac_textSignal() 
} 
.filterAs { 
... 
} 
.throttle(0.5) 
.doNext { 
... 
} 
.flattenMapAs { 
... 
self.signalForSearchWithText(text) 
} 
.deliverOn(RACScheduler.mainThreadScheduler()) 
.subscribeNextAs({ 
... 
}, { 
(error) in 
println(error) 
})
Fetching 
Sentiment Data
Fire a sentiment API request 
for each tweet and merge the 
signals?
Fetch sentiment data when 
cells become visible?
Fetch sentiment data when a 
cell has been visible for a short 
duration?
Signal All 
Things!
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) 
}
ReactiveCocoa and Swift, Better Together
I curried 
a function!
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) 
}
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) 
}
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)
ReactiveCocoa and Swift, Better Together
Better 
Together
Swift loves 
Fluent APIs
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) 
})
http://stackoverflow.com/questions/3124001/fluent-interface-pattern-in-objective-c
http://stackoverflow.com/questions/3124001/fluent-interface-pattern-in-objective-c
[[[[[[[[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); 
}];
ReactiveCocoa 
hates state
39 constants 
12 variables 
6 outlets (not my fault!) 
1 UIWindow 
5 UI state variables
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

More Related Content

ReactiveCocoa and Swift, Better Together

  • 1. ReactiveCocoa and Swift Better Together @ColinEberhardt ShinobiControls
  • 6. Functional Programming + Reactive Programming = Functional Reactive Programming
  • 8. Every line of code we write is executed in reaction to an event
  • 9. But … these events come in many different forms KVO delegates NSNotification target-action callbacks
  • 10. ReactiveCocoa provides a common interface for all events!
  • 11. … this allows us to define a language for manipulating, transforming and coordinating events
  • 13. Get Reactive let textSignal: RACSignal = usernameTextField.rac_textSignal() textSignal.subscribeNext { (text: AnyObject!) -> Void in let textString = text as String println(textString) }
  • 15. Objective-C Friction let textSignal: RACSignal = usernameTextField.rac_textSignal() textSignal.subscribeNext { (text: AnyObject!) -> Void in let textString = text as String println(textString) }
  • 16. Simplified with Swift func subscribeNextAs<T>(nextClosure:(T) -> ()) -> () { self.subscribeNext { textSignal.subscribeNextAs { (text: String) -> () in println(text) } (next: AnyObject!) -> () in let nextAsT = next! as T nextClosure(nextAsT) } } textSignal.subscribeNext { (text: AnyObject!) -> Void in let textString = text as String println(textString) }
  • 17. 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: { // ... })
  • 18. Events Signals can emit none, one or more next events, optionally followed by either an error or completed COMPLETED NEXT NEXT ERROR NEXT NEXT NEXT NEXT … intervals do not have to be regular!
  • 19. 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
  • 20. filter 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) } A filter is a ‘gate’, filtering-out events which do not match the given condition
  • 22. 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
  • 23. map let textSignal: RACSignal = usernameTextField.rac_textSignal() let textLength = textSignal.mapAs { (text: NSString) -> NSNumber in return text.length } textLength.subscribeNextAs { (length: NSNumber) in println(length) } Transforms each next event (please ignore the types!)
  • 25. Creating a pipeline let 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) }
  • 26. 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) }
  • 27. number- number- ReactiveCocoa Signals rac_textSignal- map- filter- subscribeNext-string- PUSH% Lazy Sequences string% number% number% lazy% map% filter% generator% PULL$
  • 30. searchTextField .rac_textSignal() .mapAs { (text: NSString) -> UIColor in text.length <= 3 ? UIColor.lightRedColor() : UIColor.whiteColor() } .setKeyPath("backgroundColor", onObject: searchTextField)
  • 31. Don’t search for very short search terms
  • 32. Don’t issue a new query on each key press
  • 33. searchTextField .rac_textSignal() .throttle(0.5) .filterAs { (text: NSString) -> Bool in text.length > 3 } .subscribeNextAs { (text: NSString) -> () in println(text) }
  • 36. 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 } }
  • 37. searchTextField .rac_textSignal() .throttle(0.5) .filterAs { (text: NSString) -> Bool in text.length > 3 } .mapAs { (text: NSString) -> RACStream in self.signalForSearchWithText(text) } .subscribeNextAs { ... }
  • 38. searchTextField .rac_textSignal() .throttle(0.5) .filterAs { (text: NSString) -> Bool in text.length > 3 } .flattenMapAs { (text: NSString) -> RACStream in self.signalForSearchWithText(text) } .subscribeNextAs { ... }
  • 39. 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() }
  • 40. rac_textSignal-filter- setKeyPath-thro5le- filter- fla5enMap-twi5erSearch-deliverOn- subscribeNext-
  • 41. requestAccessToTwitterSignal() .then { self.searchTextField.rac_textSignal() } .filterAs { ... } .throttle(0.5) ...
  • 42. rac_textSignal-filter- setKeyPath-thro5le- filter- fla5enMap-twi5erSearch-deliverOn- subscribeNext-requestAccess-signal- then-
  • 44. requestAccessToTwitterSignal() .then { self.searchTextField.rac_textSignal() } .filterAs { ... } .throttle(0.5) .doNext { ... } .flattenMapAs { ... self.signalForSearchWithText(text) } .deliverOn(RACScheduler.mainThreadScheduler()) .subscribeNextAs({ ... }, { (error) in println(error) })
  • 46. Fire a sentiment API request for each tweet and merge the signals?
  • 47. Fetch sentiment data when cells become visible?
  • 48. Fetch sentiment data when a cell has been visible for a short duration?
  • 50. 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) }
  • 52. I curried a function!
  • 54. 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) }
  • 55. 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) }
  • 56. 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)
  • 60. 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) })
  • 63. [[[[[[[[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); }];
  • 65. 39 constants 12 variables 6 outlets (not my fault!) 1 UIWindow 5 UI state variables
  • 66. 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