Download - LetSwift RxSwift 시작하기
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
.subscribeNext { print($0) }
aa@a@[email protected]@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 [email protected]@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)