letswift rxswift 시작하기

Post on 06-Jan-2017

4.893 Views

Category:

Software

1 Downloads

Preview:

Click to see full reader

TRANSCRIPT

let swift(16)

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

Rx?

ReactiveExtensions

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

Reactive Programming

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

Functional Reactive Programming

functional programming data flows propagation of change

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

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 }

Propagation of Change

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

Propagation of Change

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

ReactiveXhttp://reactivex.io/

An API for asynchronous programming

with observable streams

단일 다수

동기 Try<T> Iterable<T>

비동기 Future<T> Observable<T>

Observable

Operate

Subscribe

onNext

onError

onCompleted

Iterable(pull) Observable(push)

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

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

완료 !hasNext() onCompleted()

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

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

우선�만져보자

pod try RxSwift

import RxSwift

.subscribe { (event) in print(event)

}

.distinctUntilChanged()

🐶 🐱 👽🐱

🐱🐶 🐶

🐶

👽

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

import RxSwift

.subscribe { (event) in print(event)

}

.distinctUntilChanged()

🐶 🐱 👽🐱

🐱🐶 🐶

🐶

👽

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

import RxSwift

.subscribe { (event) in print(event)

}

.distinctUntilChanged()

🐶 🐱 👽🐱

🐱🐶 🐶

🐶

👽🐱

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

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

import RxSwift

.subscribe { (event) in print(event)

}

.distinctUntilChanged()

🐶 🐱 👽🐱

🐱🐶 🐶

🐶

👽

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

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

import RxSwift

.subscribe { (event) in print(event)

}

.distinctUntilChanged()

.addDisposableTo(disposeBag)

Disposable

DisposableDisposableDisposableDisposableDisposableDisposableDisposable

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

import RxSwift

.subscribe { (event) in print(event)

}

.distinctUntilChanged()

.dispose()

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

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)

🍊🍐🍎

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

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

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

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

emailTextField.rx_text.asObservable()

import RxSwiftimport RxCocoa

a@b.c

.subscribeNext { print($0) }

aa@a@ba@b.a@b.c

.addDisposableTo(disposeBag)

emailTextField.rx_text.asObservable()

import RxSwiftimport RxCocoa

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

emailTextField.rx_text.asObservable()

import RxSwiftimport RxCocoa

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

emailTextField.rx_text.asObservable()

import RxSwiftimport RxCocoa

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

.addDisposableTo(disposeBag)

.subscribeNext { print($0)}

emailTextField.rx_text.asObservable()

import RxSwiftimport RxCocoa

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

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

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

passwordTextField.rx_text.asObservable()

import RxSwiftimport RxCocoa

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

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

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

emailTextField.rx_text.asObservable()

import RxSwiftimport RxCocoa

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

.map { $0.isEmpty }

passwordTextField.rx_text.asObservable()

import RxSwiftimport RxCocoa

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

.map { $0.isEmpty }

http://rxmarbles.com/

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

a a@ a@b a@b.ca@b.

.map { $0.isEmpty }

true false false false falsefalse

“”

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)

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)

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() { … }}

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

Wrapping Alamofire.Request

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

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

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

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

let collections = Variable([Collection]())

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

let collections = Variable([Collection]())

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

.retry(1)

let collections = Variable([Collection]())

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

.startWith([])

[ ] [Collection, …, Collection]

let collections = Variable([Collection]())

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

.catchErrorJustReturn([])

[ ]

let collections = Variable([Collection]())

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

.bindTo(collections)

[ ] […] […] […]

https://github.com/RxSwiftCommunity/RxAlamofire

RxAlamofire

더�알아보기

https://github.com/RxSwiftCommunity

RxSwift Community

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

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

https://github.com/devxoul/RxTodo

RxTodo

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

감사합니다.

let swift(16)

top related