letswift rxswift 시작하기

79
let swift(16) RxSwift 시작하기 최완복

Upload: wanbok-choi

Post on 06-Jan-2017

4.893 views

Category:

Software


1 download

TRANSCRIPT

Page 1: LetSwift RxSwift 시작하기

let swift(16)

RxSwift�시작하기

최완복

Page 2: LetSwift RxSwift 시작하기
Page 3: LetSwift RxSwift 시작하기

class ViewController: UIViewController { @IBOutlet weak var textField: UITextField! @IBOutlet weak var button: UIButton! @IBOutlet weak var passwordField: UITextField! @IBOutlet weak var signInButton: UIButton! override func viewDidLoad() { super.viewDidLoad() self.textField.rac_textSignal().subscribeNext { (x) -> Void in println(x) } let textSignal = self.textField.rac_textSignal().map { (x) -> AnyObject! in NSNumber(bool: ((x as? NSString) ?? "").rangeOfString("@").location != NSNotFound) } //NSNumber (boolean) let colorSignal = textSignal.map { x in ((x as? NSNumber)?.boolValue ?? false) ? UIColor.greenColor() : UIColor.redColor() } // UICOlor //textSignal ~> RAC(button, "enabled") colorSignal ~> RAC(textField, "textColor") let passwordSignal = self.passwordField.rac_textSignal().map { x in NSNumber(bool: (x as? NSString ?? "").length > 4) } //NSNumber let formValidSignal = RACSignal.combineLatest([textSignal, passwordSignal]).map { let tuple = $0 as! RACTuple let bools = tuple.allObjects() as! [Bool] return NSNumber(bool: bools[0] == true && bools[1] == true) } formValidSignal ~> RAC(signInButton, "enabled") button.rac_command = RACCommand(enabled: textSignal, signalBlock: { (x) -> RACSignal! in println("pressed") return RACSignal.empty() }) } }

Page 4: LetSwift RxSwift 시작하기
Page 5: LetSwift RxSwift 시작하기

Rx?

Page 6: LetSwift RxSwift 시작하기

ReactiveExtensions

Page 7: LetSwift RxSwift 시작하기

In computing, reactive programming is a programming paradigm oriented around data flows and the propagation of change.

Reactive Programming

Page 8: LetSwift RxSwift 시작하기

Functional reactive programming (FRP) is a programming paradigm for reactive programming (asynchronous dataflow programming) using the building blocks of functional programming (e.g. map, reduce, filter).

Functional Reactive Programming

Page 9: LetSwift RxSwift 시작하기

Functional Reactive Programming

functional programming data flows propagation of change

Page 10: LetSwift RxSwift 시작하기

Functional Programming

1�부터�10까지�더하는�문제

var sum = 0 for i in 1...10 { sum += 1 } print(sum)

Procedural way

print((1...10).reduce(0) { $0 + $1 })Functional way

Page 11: LetSwift RxSwift 시작하기

Data Flow

(1...10) .filter { $0 % 2 == 0 // 2, 4, 6, 8, 10 } .map { $0 * 10 // 20, 40, 60, 80, 100 } .reduce(0) { $0 + $1 // 300 }

Page 12: LetSwift RxSwift 시작하기

Propagation of Change

(1...5) .filter { $0 % 2 == 0 // 2, 4 } .map { $0 * 10 // 20, 40 } .reduce(0) { $0 + $1 // 60 }

Page 13: LetSwift RxSwift 시작하기

Propagation of Change

(1...5) .filter { $0 % 2 == 0 // 2, 4 } .map { $0 * 100 // 200, 400 } .reduce(0) { $0 + $1 // 600 }

Page 14: LetSwift RxSwift 시작하기
Page 15: LetSwift RxSwift 시작하기

ReactiveXhttp://reactivex.io/

Page 16: LetSwift RxSwift 시작하기

An API for asynchronous programming

with observable streams

Page 17: LetSwift RxSwift 시작하기

단일 다수

동기 Try<T> Iterable<T>

비동기 Future<T> Observable<T>

Page 18: LetSwift RxSwift 시작하기

Observable

Operate

Subscribe

Page 19: LetSwift RxSwift 시작하기

onNext

onError

onCompleted

Page 20: LetSwift RxSwift 시작하기

Iterable(pull) Observable(push)

데이터받기 T�next() onNext�(T)

에러�발견 throws�Exception onError(Exception)

완료 !hasNext() onCompleted()

Page 21: LetSwift RxSwift 시작하기

Observable.create<String> { observer in observer.onNext("🐶") observer.onNext("🐱") observer.onNext("👽") observer.onCompleted()}.subscribe { print($0)}

>> “🐶”..”🐱”.."👽" >>

Page 22: LetSwift RxSwift 시작하기
Page 23: LetSwift RxSwift 시작하기

우선�만져보자

Page 24: LetSwift RxSwift 시작하기

pod try RxSwift

Page 25: LetSwift RxSwift 시작하기
Page 26: LetSwift RxSwift 시작하기

import RxSwift

.subscribe { (event) in print(event)

}

.distinctUntilChanged()

🐶 🐱 👽🐱

🐱🐶 🐶

🐶

👽

Page 27: LetSwift RxSwift 시작하기

["🐶", "🐱", "🐱", "🐶", "👽"].toObservable()

import RxSwift

.subscribe { (event) in print(event)

}

.distinctUntilChanged()

🐶 🐱 👽🐱

🐱🐶 🐶

🐶

👽

Page 28: LetSwift RxSwift 시작하기

["🐶", "🐱", "🐱", "🐶", "👽"].toObservable()

import RxSwift

.subscribe { (event) in print(event)

}

.distinctUntilChanged()

🐶 🐱 👽🐱

🐱🐶 🐶

🐶

👽🐱

Next(🐶)Next(🐱)Next(🐱)Next(🐶)Next(👽)Completed

Page 29: LetSwift RxSwift 시작하기

["🐶", "🐱", "🐱", "🐶", "👽"].toObservable()

import RxSwift

.subscribe { (event) in print(event)

}

.distinctUntilChanged()

🐶 🐱 👽🐱

🐱🐶 🐶

🐶

👽

Next(🐶)Next(🐱)Next(🐶)Next(👽)Completed

Page 30: LetSwift RxSwift 시작하기

["🐶", "🐱", "🐱", "🐶", "👽"].toObservable()

import RxSwift

.subscribe { (event) in print(event)

}

.distinctUntilChanged()

.addDisposableTo(disposeBag)

Disposable

Page 31: LetSwift RxSwift 시작하기

DisposableDisposableDisposableDisposableDisposableDisposableDisposable

Page 32: LetSwift RxSwift 시작하기

["🐶", "🐱", "🐱", "🐶", "👽"].toObservable()

import RxSwift

.subscribe { (event) in print(event)

}

.distinctUntilChanged()

.dispose()

Page 33: LetSwift RxSwift 시작하기
Page 34: LetSwift RxSwift 시작하기
Page 35: LetSwift RxSwift 시작하기
Page 36: LetSwift RxSwift 시작하기

Creating Observables asObservable, create, deferred, empty, error, toObservable (array), interval, never, just, of, range, repeatElement, timer

Transforming Observables buffer, flatMap, flatMapFirst, flatMapLatest, map, scan, window

Filtering Observables debounce / throttle, distinctUntilChanged, elementAt, filter, sample, skip, take, takeLast, single

Combining Observables merge, startWith, switchLatest, combineLatest, zip

Error Handling Operators catch, retry, retryWhen

Observable Utility Operators delaySubscription, do / doOnNext, observeOn / observeSingleOn, subscribe, subscribeOn, timeout, using, debug

Conditional and Boolean Operators amb, skipWhile, skipUntil, takeUntil, takeWhile

Mathematical and Aggregate Operators concat, reduce / aggregate, toArray

Connectable Observable Operators multicast, publish, refCount, replay, shareReplay

Page 37: LetSwift RxSwift 시작하기

let sequenceThatErrors = Observable<String>.create { observer in observer.onNext("🍎 ") observer.onNext("🍐 ") observer.onNext("🍊 ") if isGoingWrong { observer.onError(Error.Test) print("Error encountered") count += 1 } observer.onNext("🐶 ") observer.onNext("🐱 ") observer.onNext("🐭 ") observer.onCompleted() return NopDisposable.instance }

————result————🍎

🍐

🍊

Error encountered sequenceThatErrors .subscribeNext { print($0) } .addDisposableTo(disposeBag)

🍊🍐🍎

Page 38: LetSwift RxSwift 시작하기

import UIKitimport RxSwiftimport RxCocoa

class EmailLoginViewController: UIViewController {

let disposeBag = DisposeBag()

@IBOutlet weak var emailTextField: UITextField! @IBOutlet weak var passwordTextField: UITextField! @IBOutlet weak var loginButton: UIButton! … @IBAction func onLogin(sender: UIButton) { guard let email = emailTextField?.text, password = passwordTextField?.text else { return } Router.EmailLogin(["email": email, "password": password]).request .responseJSON { [weak self] response in let json = JSON(response.result.value!) let user = User(json: json) } } // MARK: UIViewController implements override func viewDidLoad() { super.viewDidLoad() }}

Login Validator

Page 39: LetSwift RxSwift 시작하기

import UIKitimport RxSwiftimport RxCocoa

class EmailLoginViewController: UIViewController {

let disposeBag = DisposeBag()

@IBOutlet weak var emailTextField: UITextField! @IBOutlet weak var passwordTextField: UITextField! @IBOutlet weak var loginButton: UIButton! … @IBAction func onLogin(sender: UIButton) { guard let email = emailTextField?.text, password = passwordTextField?.text else { return } Router.EmailLogin(["email": email, "password": password]).request .responseJSON { [weak self] response in let json = JSON(response.result.value!) let user = User(json: json) self?.succeedToLogin(user) } } // MARK: UIViewController implements override func viewDidLoad() { super.viewDidLoad() }}

Page 40: LetSwift RxSwift 시작하기

1. 이메일형식을�체크�2. 패스워드는�6자리�이상�3. 이메일/패스워드가�비어있을때,�입력을�요구한다.

Page 41: LetSwift RxSwift 시작하기

1. 이메일형식을�체크�2. 패스워드는�6자리�이상�3. 이메일/패스워드가�비어있을때,�입력을�요구한다.

Page 42: LetSwift RxSwift 시작하기

emailTextField.rx_text.asObservable()

import RxSwiftimport RxCocoa

[email protected]

.subscribeNext { print($0) }

aa@a@[email protected]@b.c

.addDisposableTo(disposeBag)

Page 43: LetSwift RxSwift 시작하기

emailTextField.rx_text.asObservable()

import RxSwiftimport RxCocoa

.subscribeNext { let isValid = $0.isEmail || $0.isEmpty}.addDisposableTo(disposeBag)

Page 44: LetSwift RxSwift 시작하기

emailTextField.rx_text.asObservable()

import RxSwiftimport RxCocoa

.subscribeNext { let isValid = $0.isEmail || $0.isEmpty}.addDisposableTo(disposeBag)

Page 45: LetSwift RxSwift 시작하기

emailTextField.rx_text.asObservable()

import RxSwiftimport RxCocoa

.map { $0.isEmail || $0.isEmpty }

.addDisposableTo(disposeBag)

.subscribeNext { print($0)}

Page 46: LetSwift RxSwift 시작하기

emailTextField.rx_text.asObservable()

import RxSwiftimport RxCocoa

.map { $0.isEmail || $0.isEmpty }

.subscribeNext { self.emailTextField.backgroundColor = $0 ? UIColor.whiteColor() : UIColor.alertColor}.addDisposableTo(disposeBag)

Page 47: LetSwift RxSwift 시작하기

1. 이메일형식을�체크�2. 패스워드는�6자리�이상�3. 이메일/패스워드가�비어있을때,�입력을�요구한다.

Page 48: LetSwift RxSwift 시작하기

passwordTextField.rx_text.asObservable()

import RxSwiftimport RxCocoa

.map { !(1..<6 ~= $0.characters.count) }

.subscribeNext { self.passwordTextField.backgroundColor = $0 ? UIColor.whiteColor() : UIColor.alertColor}.addDisposableTo(disposeBag)

Page 49: LetSwift RxSwift 시작하기

1. 이메일형식을�체크�2. 패스워드는�6자리�이상�3. 이메일/패스워드가�비어있을때,�입력을�요구한다.

Page 50: LetSwift RxSwift 시작하기

emailTextField.rx_text.asObservable()

import RxSwiftimport RxCocoa

.subscribeNext { let buttonTitle = $0 ? "이메일을 입력하세요" : "로그인 하기" self.loginButton?.setTitle(buttonTitle, forState: .Normal)}.addDisposableTo(disposeBag)

.map { $0.isEmpty }

Page 51: LetSwift RxSwift 시작하기

passwordTextField.rx_text.asObservable()

import RxSwiftimport RxCocoa

.subscribeNext { let buttonTitle = $0 ? "패스워드를�입력하세요"�:�"로그인�하기" self.loginButton?.setTitle(buttonTitle, forState: .Normal)}.addDisposableTo(disposeBag)

.map { $0.isEmpty }

Page 52: LetSwift RxSwift 시작하기
Page 53: LetSwift RxSwift 시작하기

http://rxmarbles.com/

Page 54: LetSwift RxSwift 시작하기
Page 55: LetSwift RxSwift 시작하기

let emailEmptyObservable = emailTextField.rx_text.asObservable() .map { $0.isEmpty } let passwordEmptyObservable = passwordTextField.rx_text.asObservable() .map { $0.isEmpty }

a a@ a@b [email protected]@b.

.map { $0.isEmpty }

true false false false falsefalse

“”

Page 56: LetSwift RxSwift 시작하기

Observable .combineLatest(emailEmptyObservable, passwordEmptyObservable) { return ($0, $1) }

true false false

false falsetrue

(true, true) (false, true) (false, true)

false

.combineLatest(email, pass) { return ($0, $1) }

(false, false) (false, false)(false, false)

Page 57: LetSwift RxSwift 시작하기

let emailEmptyObservable = emailTextField.rx_text.asObservable() .map { $0.isEmpty } let passwordEmptyObservable = passwordTextField.rx_text.asObservable() .map { $0.isEmpty } Observable .combineLatest(emailEmptyObservable, passwordEmptyObservable) { return ($0, $1) } .subscribeNext { tuple in let buttonTitle: String = { switch tuple { case (true, true): return "이메일/패스워드를�입력하세요" case (true, _): return "이메일을�입력하세요" case (_, true): return "패스워드를�입력하세요" default: return "로그인�하기" } }() self.registerButton?.setTitle(buttonTitle, forState: .Normal) self.registerButton?.backgroundColor = tuple.0 || tuple.1 ? UIColor.gray4AColor : UIColor.tintColor } .addDisposableTo(disposeBag)

Page 58: LetSwift RxSwift 시작하기
Page 59: LetSwift RxSwift 시작하기
Page 60: LetSwift RxSwift 시작하기

import UIKitimport RxSwiftimport RxCocoa

class EmailLoginViewController: UIViewController {

let disposeBag = DisposeBag()

@IBOutlet weak var emailTextField: UITextField! @IBOutlet weak var passwordTextField: UITextField! @IBOutlet weak var loginButton: UIButton! … // MARK: UIViewController implements override func viewDidLoad() { super.viewDidLoad() addValidations() }

func addValidations() { … }}

Page 61: LetSwift RxSwift 시작하기

func addValidations() { let emailEmptyObservable = emailTextField.rx_text.asObservable().map { $0.isEmpty } let passwordEmptyObservable = passwordTextField.rx_text.asObservable().map { $0.isEmpty } Observable .combineLatest(emailEmptyObservable, passwordEmptyObservable) { return ($0, $1) } .subscribeNext { tuple in let buttonTitle: String = { switch tuple { case (true, true): return "이메일/패스워드를�입력하세요" case (true, _): return "이메일을�입력하세요" case (_, true): return "패스워드를�입력하세요" default: return "로그인�하기" } }() self.registerButton?.setTitle(buttonTitle, forState: .Normal) self.registerButton?.backgroundColor = tuple.0 || tuple.1 ? UIColor.gray4AColor : UIColor.tintColor } .addDisposableTo(disposeBag) emailTextField.rx_text.asObservable() .map { $0.isEmail || $0.isEmpty } .subscribeNext { self.emailTextField.backgroundColor = $0 ? UIColor.whiteColor() : UIColor.alertColor } .addDisposableTo(disposeBag) passwordTextField.rx_text.asObservable() .map { !(1..<6 ~= $0.characters.count) } .subscribeNext { self.passwordTextField.backgroundColor = $0 ? UIColor.whiteColor() : UIColor.alertColor } .addDisposableTo(disposeBag) }

Page 62: LetSwift RxSwift 시작하기

Wrapping Alamofire.Request

Page 63: LetSwift RxSwift 시작하기

enum Router: URLRequestConvertible { case Collection(Int)

var request: Alamofire.Request { return HTTPManager.sharedHTTPManager.request(self) } var method: Alamofire.Method { return .GET } var path: String { switch self { case .Collection(let id): return “/Collections/\(id)" } } var parameters: Parameter { switch self { case .Collection: return ["filter": ["include": ["images"]]] } } // MARK: URLRequestConvertible var URLRequest: NSMutableURLRequest { let URL = NSURL(string: Router.baseURLString)! let mutableURLRequest = NSMutableURLRequest(URL: URL.URLByAppendingPathComponent(path)) mutableURLRequest.HTTPMethod = method.rawValue return Alamofire.ParameterEncoding.URL.encode(mutableURLRequest, parameters: parameters).0 } } }

Page 64: LetSwift RxSwift 시작하기

let collections = Variable([Collection]())

Router.Collections(["filter": filterParameter]).request .validate() .responseJSON { [weak self] response in if let value = response.result.value { let newData = Collection.array(JSON(value)) self?.collections.value = newData return } }

Page 65: LetSwift RxSwift 시작하기

extension Alamofire.Request { func rx_validateSuccessfulResponse() -> Request { …… } public func rx_result<T: ResponseSerializerType>( queue queue: dispatch_queue_t? = nil, responseSerializer: T) -> Observable<T.SerializedObject> { return Observable.create { [weak self] observer in self? .rx_validateSuccessfulResponse() .response(queue: queue, responseSerializer: responseSerializer) { _response in switch _response.result { case .Success(let result): if let _ = _response.response { observer.on(.Next(result)) } else { observer.on(.Error(NSError(domain: "Frip", code: -1, userInfo: nil))) } observer.on(.Completed) case .Failure(let error): observer.on(.Error(error as ErrorType)) } } return NopDisposable.instance } } public func rx_JSON(options options: NSJSONReadingOptions = .AllowFragments) -> Observable<AnyObject> { return rx_result(responseSerializer: Request.JSONResponseSerializer(options: options)) } }

Page 66: LetSwift RxSwift 시작하기

.response(queue: queue, responseSerializer: responseSerializer) { _response in switch _response.result { case .Success(let result): if let _ = _response.response { observer.on(.Next(result)) } else { observer.on(.Error(NSError(domain: "Frip", code: -1, userInfo: nil))) } observer.on(.Completed) case .Failure(let error): observer.on(.Error(error as ErrorType)) } }

Page 67: LetSwift RxSwift 시작하기

let collections = Variable([Collection]())

Router.Collections(["filter": filterParameter]).request .rx_JSON() .retry(1) .startWith([]) .catchErrorJustReturn([]) .bindTo(collections)

Page 68: LetSwift RxSwift 시작하기

let collections = Variable([Collection]())

Router.Collections(["filter": filterParameter]).request .rx_JSON() .retry(1) .startWith([]) .catchErrorJustReturn([]) .bindTo(collections)

.retry(1)

Page 69: LetSwift RxSwift 시작하기

let collections = Variable([Collection]())

Router.Collections(["filter": filterParameter]).request .rx_JSON() .retry(1) .startWith([]) .catchErrorJustReturn([]) .bindTo(collections)

.startWith([])

[ ] [Collection, …, Collection]

Page 70: LetSwift RxSwift 시작하기

let collections = Variable([Collection]())

Router.Collections(["filter": filterParameter]).request .rx_JSON() .retry(1) .startWith([]) .catchErrorJustReturn([]) .bindTo(collections)

.catchErrorJustReturn([])

[ ]

Page 71: LetSwift RxSwift 시작하기

let collections = Variable([Collection]())

Router.Collections(["filter": filterParameter]).request .rx_JSON() .retry(1) .startWith([]) .catchErrorJustReturn([]) .bindTo(collections)

.bindTo(collections)

[ ] […] […] […]

Page 72: LetSwift RxSwift 시작하기

https://github.com/RxSwiftCommunity/RxAlamofire

RxAlamofire

Page 73: LetSwift RxSwift 시작하기

더�알아보기

Page 74: LetSwift RxSwift 시작하기

https://github.com/RxSwiftCommunity

RxSwift Community

Page 75: LetSwift RxSwift 시작하기
Page 76: LetSwift RxSwift 시작하기

https://justhackem.wordpress.com/2015/03/19/rmvvm-architecture/

Reactive�MVVM(Model-View-ViewModel)�모바일�응용프로그램�아키텍쳐

https://github.com/devxoul/RxTodo

RxTodo

Page 77: LetSwift RxSwift 시작하기

References

ReactiveX.io https://github.com/ReactiveX/RxSwift http://www.introtorx.com/ http://mobicon.tistory.com/467 http://www.slideshare.net/sunhyouplee/functional-reactive-programming-with-rxswift-62123571 http://www.slideshare.net/deview/1b4-39616041 http://www.slideshare.net/gmind7/springcamp2015-rxjava http://www.slideshare.net/jongwookkim/ndc14-rx-functional-reactive-programminghttps://justhackem.wordpress.com/2015/03/19/rmvvm-architecture/ https://github.com/devxoul/RxTodo

Page 78: LetSwift RxSwift 시작하기

감사합니다.

Page 79: LetSwift RxSwift 시작하기

let swift(16)