ReactiveCocoa and Swift, Better Together
- 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
- 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)
- 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
}
}
- 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()
}
- 44. requestAccessToTwitterSignal()
.then {
self.searchTextField.rac_textSignal()
}
.filterAs {
...
}
.throttle(0.5)
.doNext {
...
}
.flattenMapAs {
...
self.signalForSearchWithText(text)
}
.deliverOn(RACScheduler.mainThreadScheduler())
.subscribeNextAs({
...
}, {
(error) in
println(error)
})
- 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)
}
- 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