reactive cocoa made simple with swift

43
ReactiveCocoa made Simple with Swift! @ColinEberhardt ShinobiControls

Upload: colin-eberhardt

Post on 07-Dec-2014

1.913 views

Category:

Technology


2 download

DESCRIPTION

A talk I delivered at iOSDevUK14 where I mixed ReactiveCocoa with Swift.

TRANSCRIPT

Page 1: Reactive cocoa made Simple with Swift

ReactiveCocoa made Simple with Swift!

@ColinEberhardt ShinobiControls

Page 2: Reactive cocoa made Simple with Swift

Tutorials

Page 3: Reactive cocoa made Simple with Swift

What is ReactiveCocoa?

• ReactiveCocoa is an open-source framework created by the GitHub team

• Inspired by Functional Reactive Programming (FRP)

• Changes the way in which we structure our applications

• Inspired by Microsoft’s ReactiveExtensions (Rx)

Page 4: Reactive cocoa made Simple with Swift

Functional Reactive Programming

• Functional Programming

• First-class functions, passed as arguments to other functions

• Reactive Programming

• Focusses on data flow

• Functional Programming + Reactive Programming = FRP

Page 5: Reactive cocoa made Simple with Swift

FRP - WTF?

http://stackoverflow.com/questions/1028250/what-is-functional-reactive-programming

Page 6: Reactive cocoa made Simple with Swift

In my own words …

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

• But … these events all have different interfaces

• delegates, target-action, KVO, closures

• ReactiveCocoa provides a standard interface for all events

• Allows us to define a higher level language

• Easier to test too!

Page 7: Reactive cocoa made Simple with Swift

Learn through play

Page 8: Reactive cocoa made Simple with Swift

Code Build Run

Explain! Repeat

Page 9: Reactive cocoa made Simple with Swift

Get Reactive

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

Page 10: Reactive cocoa made Simple with Swift

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) } }

Page 11: Reactive cocoa made Simple with Swift

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 12: Reactive cocoa made Simple with Swift

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 13: Reactive cocoa made Simple with Swift

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 14: Reactive cocoa made Simple with Swift

Operations

• ReactiveCocoa allows you to perform operations on signals

• map, filter, skip, take, throttle …

• These operations are entirely agnostic to the source of the signal

Page 15: Reactive cocoa made Simple with Swift

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 16: Reactive cocoa made Simple with Swift

What exactly are events?

• What does a next event actually look like?

• Anything!

• Signals are an interface for handling asynchronous events

• The event contents is context dependant

Page 17: Reactive cocoa made Simple with Swift

map

Transforms each next event

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 18: Reactive cocoa made Simple with Swift

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 19: Reactive cocoa made Simple with Swift

Fluent syntax

Operations return RACSignal, allowing method chaining

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 20: Reactive cocoa made Simple with Swift

rac_textSIgnal- filter- subscribeNext-

Value->-3-

map-

NSString- NSNumber-

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 21: Reactive cocoa made Simple with Swift

Valid text fields

• Unfortunately we need to ‘box’ bool values.

• I am sure ReactiveSwift will fix this ;-)

let validUsernameSignal = usernameTextField.rac_textSignal() .mapAs { (text: NSString) -> NSNumber in return self.isValidUsername(text) } !validUsernameSignal.mapAs { (valid: NSNumber) -> UIColor in return valid.boolValue ? UIColor.clearColor() : UIColor.yellowColor() }.setKeyPath("backgroundColor", onObject: usernameTextField)

Page 22: Reactive cocoa made Simple with Swift

More Swift Magic!

!!RAC(usernameTextField, "backgroundColor") << validUsernameSignal.mapAs { (valid: NSNumber) -> UIColor in return valid.boolValue ? UIColor.clearColor() : UIColor.yellowColor() }

http://napora.org/a-swift-reaction/

Page 23: Reactive cocoa made Simple with Swift

More Sweet Sweet Swift!

func validToBackground(valid: NSNumber) -> UIColor { return valid.boolValue ? UIColor.clearColor() : UIColor.yellowColor() } !func isValidText(validator:(String) -> Bool)(text: NSString) -> NSNumber { return validator(text) } !let validUsernameSignal = usernameTextField.rac_textSignal() .mapAs(isValidText(isValidUsername)) .distinctUntilChanged() !RAC(usernameTextField, "backgroundColor") << validUsernameSignal.mapAs(validToBackground)

Page 24: Reactive cocoa made Simple with Swift
Page 25: Reactive cocoa made Simple with Swift
Page 26: Reactive cocoa made Simple with Swift

Login button enabled state

Combines two signals into a new one

let signUpActiveSignal = RACSignalEx.combineLatestAs( [validUsernameSignal, validPasswordSignal]) { (validUsername: NSNumber, validPassword: NSNumber) -> NSNumber in return validUsername && validPassword } !signUpActiveSignal.subscribeNextAs { (active: NSNumber) in self.signInButton.enabled = active }

Page 27: Reactive cocoa made Simple with Swift

The pipeline

rac_textSIgnal- map- backgroundColor-map-BOOL-NSString- UIColor-

password-

rac_textSIgnal- map- backgroundColor-map-BOOL-NSString- UIColor-

username-

combineLatest:reduce- subscribeNext-BOOL-

Page 28: Reactive cocoa made Simple with Swift

Reactive Button Click

Replaces target-action with a signal

signInButton.rac_signalForControlEvents(.TouchUpInside) .subscribeNext { (button) in println("clicked") }

Page 29: Reactive cocoa made Simple with Swift

Creating Signals

func signInSignal() -> RACSignal { return RACSignal.createSignal { (subscriber) -> RACDisposable! in println("Sign-in initiated") self.signInService.signInWithUsername(self.usernameTextField.text, password: self.passwordTextField.text) { (success) in println("Sign-in completed") subscriber.sendNext(success) subscriber.sendCompleted() } return nil } }

Page 30: Reactive cocoa made Simple with Swift

Using the sign-in signal

signInButton.rac_signalForControlEvents(.TouchUpInside) .map { (any) -> RACSignal in self.signInSignal() }.subscribeNext { (any) in println(any) }

Page 31: Reactive cocoa made Simple with Swift

Doing it right!

flattenMap subscribes to a signal, returning the resultant events

signInButton.rac_signalForControlEvents(.TouchUpInside) .flattenMap { (any) -> RACSignal in self.signInSignal() }.subscribeNext { (any) in println(any) }

Page 32: Reactive cocoa made Simple with Swift

Side-effects

Side-effects receive next events, but cannot mutate them

signInButton.rac_signalForControlEvents(.TouchUpInside) .doNext { (any) in self.signInButton.enabled = false; }.flattenMap { (any) -> RACSignal in self.signInSignal() }.subscribeNextAs { (success: NSNumber) in self.signInButton.enabled = true; self.handleSignInResult(success.boolValue) }

Page 33: Reactive cocoa made Simple with Swift

Login pipeline

rac_signalForControlEvents11 fla3enMap1 subscribeNext1UIBu3on1 BOOL1

doNext1signInSignal1

BOOL1

Page 34: Reactive cocoa made Simple with Swift

ReactiveCocoa Made Simple

• A signal emits events

• next

• error

• completed

• A signal can have multiple subscribers

• Signals can emit multiple next events, optionally followed by either an error or completed

Page 35: Reactive cocoa made Simple with Swift

Random cool stuff!

Page 36: Reactive cocoa made Simple with Swift
Page 37: Reactive cocoa made Simple with Swift

[[[[[[[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]; }] deliverOn:[RACScheduler mainThreadScheduler]] subscribeNext:^(NSDictionary *jsonSearchResult) { NSArray *statuses = jsonSearchResult[@"statuses"]; NSArray *tweets = [statuses linq_select:^id(id tweet) { return [RWTweet tweetWithStatus:tweet]; }]; [self.resultsViewController displayTweets:tweets]; } error:^(NSError *error) { NSLog(@"An error occurred: %@", error); }];

Page 38: Reactive cocoa made Simple with Swift
Page 39: Reactive cocoa made Simple with Swift

https://github.com/ColinEberhardt/ReactiveSwiftFlickrSearch

Page 40: Reactive cocoa made Simple with Swift
Page 41: Reactive cocoa made Simple with Swift

// a signal that emits events when visibility changes let visibleStateChanged = RACObserve(self, "isVisible").skip(1) !// filtered into visible and hidden signals let visibleSignal = visibleStateChanged.filter { $0.boolValue } let hiddenSignal = visibleStateChanged.filter { !$0.boolValue } !// a signal that emits when an item has been visible for 1 second let fetchMetadata = visibleSignal.delay(1).takeUntil(hiddenSignal) !fetchMetadata.subscribeNext { (next: AnyObject!) -> () in // fetch data }

Page 42: Reactive cocoa made Simple with Swift

Resources• https://github.com/ReactiveCocoa/ReactiveCocoa

• Read the Change Logs!

• My Stuff …

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

• https://github.com/ColinEberhardt

• The code from this presentation

• https://github.com/ColinEberhardt/SwiftReactivePlayground

Page 43: Reactive cocoa made Simple with Swift

ReactiveCocoa made Simple with Swift!

@ColinEberhardt ShinobiControls