KEMBAR78
LetSwift RxSwift 시작하기 | PDF
letswift(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?
Reactive
Extensions
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
}
ReactiveX
http://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 UIKit
import RxSwift
import 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 UIKit
import RxSwift
import 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 RxSwift
import RxCocoa
a@b.c
.subscribeNext { print($0) }
a
a@
a@b
a@b.
a@b.c
.addDisposableTo(disposeBag)
emailTextField.rx_text.asObservable()
import RxSwift
import RxCocoa
.subscribeNext {
let isValid = $0.isEmail || $0.isEmpty
}
.addDisposableTo(disposeBag)
emailTextField.rx_text.asObservable()
import RxSwift
import RxCocoa
.subscribeNext {
let isValid = $0.isEmail || $0.isEmpty
}
.addDisposableTo(disposeBag)
emailTextField.rx_text.asObservable()
import RxSwift
import RxCocoa
.map { $0.isEmail || $0.isEmpty }
.addDisposableTo(disposeBag)
.subscribeNext {
print($0)
}
emailTextField.rx_text.asObservable()
import RxSwift
import 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 RxSwift
import 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 RxSwift
import RxCocoa
.subscribeNext {
let buttonTitle = $0 ? "이메일을 입력하세요" : "로그인 하기"
self.loginButton?.setTitle(buttonTitle, forState: .Normal)
}
.addDisposableTo(disposeBag)
.map { $0.isEmpty }
passwordTextField.rx_text.asObservable()
import RxSwift
import 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 UIKit
import RxSwift
import 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
감사합니다.
letswift(16)

LetSwift RxSwift 시작하기

  • 1.
  • 3.
    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() }) } }
  • 5.
  • 6.
  • 7.
    In computing, reactiveprogramming is a programming paradigm oriented around data flows and the propagation of change. Reactive Programming
  • 8.
    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
  • 9.
    Functional Reactive Programming functionalprogramming data flows propagation of change
  • 10.
    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
  • 11.
    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 }
  • 12.
    Propagation of Change (1...5) .filter{ $0 % 2 == 0 // 2, 4 } .map { $0 * 10 // 20, 40 } .reduce(0) { $0 + $1 // 60 }
  • 13.
    Propagation of Change (1...5) .filter{ $0 % 2 == 0 // 2, 4 } .map { $0 * 100 // 200, 400 } .reduce(0) { $0 + $1 // 600 }
  • 15.
  • 16.
    An API forasynchronous programming with observable streams
  • 17.
    단일 다수 동기 Try<T>Iterable<T> 비동기 Future<T> Observable<T>
  • 18.
  • 19.
  • 20.
    Iterable(pull) Observable(push) 데이터받기 Tnext() onNext (T) 에러 발견 throws Exception onError(Exception) 완료 !hasNext() onCompleted()
  • 21.
    Observable.create<String> { observerin observer.onNext("🐶") observer.onNext("🐱") observer.onNext("👽") observer.onCompleted() } .subscribe { print($0) } >> “🐶”..”🐱”.."👽" >>
  • 23.
  • 24.
  • 26.
    import RxSwift .subscribe {(event) in print(event) } .distinctUntilChanged() 🐶 🐱 👽🐱 🐱🐶 🐶 🐶 👽
  • 27.
    ["🐶", "🐱", "🐱","🐶", "👽"].toObservable() import RxSwift .subscribe { (event) in print(event) } .distinctUntilChanged() 🐶 🐱 👽🐱 🐱🐶 🐶 🐶 👽
  • 28.
    ["🐶", "🐱", "🐱","🐶", "👽"].toObservable() import RxSwift .subscribe { (event) in print(event) } .distinctUntilChanged() 🐶 🐱 👽🐱 🐱🐶 🐶 🐶 👽🐱 Next(🐶) Next(🐱) Next(🐱) Next(🐶) Next(👽) Completed
  • 29.
    ["🐶", "🐱", "🐱","🐶", "👽"].toObservable() import RxSwift .subscribe { (event) in print(event) } .distinctUntilChanged() 🐶 🐱 👽🐱 🐱🐶 🐶 🐶 👽 Next(🐶) Next(🐱) Next(🐶) Next(👽) Completed
  • 30.
    ["🐶", "🐱", "🐱","🐶", "👽"].toObservable() import RxSwift .subscribe { (event) in print(event) } .distinctUntilChanged() .addDisposableTo(disposeBag) Disposable
  • 31.
  • 32.
    ["🐶", "🐱", "🐱","🐶", "👽"].toObservable() import RxSwift .subscribe { (event) in print(event) } .distinctUntilChanged() .dispose()
  • 36.
    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
  • 37.
    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) 🍊🍐🍎
  • 38.
    import UIKit import RxSwift importRxCocoa 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
  • 39.
    import UIKit import RxSwift importRxCocoa 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() } }
  • 40.
    1. 이메일형식을 체크 2.패스워드는 6자리 이상 3. 이메일/패스워드가 비어있을때, 입력을 요구한다.
  • 41.
    1. 이메일형식을 체크 2.패스워드는 6자리 이상 3. 이메일/패스워드가 비어있을때, 입력을 요구한다.
  • 42.
    emailTextField.rx_text.asObservable() import RxSwift import RxCocoa a@b.c .subscribeNext{ print($0) } a a@ a@b a@b. a@b.c .addDisposableTo(disposeBag)
  • 43.
    emailTextField.rx_text.asObservable() import RxSwift import RxCocoa .subscribeNext{ let isValid = $0.isEmail || $0.isEmpty } .addDisposableTo(disposeBag)
  • 44.
    emailTextField.rx_text.asObservable() import RxSwift import RxCocoa .subscribeNext{ let isValid = $0.isEmail || $0.isEmpty } .addDisposableTo(disposeBag)
  • 45.
    emailTextField.rx_text.asObservable() import RxSwift import RxCocoa .map{ $0.isEmail || $0.isEmpty } .addDisposableTo(disposeBag) .subscribeNext { print($0) }
  • 46.
    emailTextField.rx_text.asObservable() import RxSwift import RxCocoa .map{ $0.isEmail || $0.isEmpty } .subscribeNext { self.emailTextField.backgroundColor = $0 ? UIColor.whiteColor() : UIColor.alertColor } .addDisposableTo(disposeBag)
  • 47.
    1. 이메일형식을 체크 2.패스워드는 6자리 이상 3. 이메일/패스워드가 비어있을때, 입력을 요구한다.
  • 48.
    passwordTextField.rx_text.asObservable() import RxSwift import RxCocoa .map{ !(1..<6 ~= $0.characters.count) } .subscribeNext { self.passwordTextField.backgroundColor = $0 ? UIColor.whiteColor() : UIColor.alertColor } .addDisposableTo(disposeBag)
  • 49.
    1. 이메일형식을 체크 2.패스워드는 6자리 이상 3. 이메일/패스워드가 비어있을때, 입력을 요구한다.
  • 50.
    emailTextField.rx_text.asObservable() import RxSwift import RxCocoa .subscribeNext{ let buttonTitle = $0 ? "이메일을 입력하세요" : "로그인 하기" self.loginButton?.setTitle(buttonTitle, forState: .Normal) } .addDisposableTo(disposeBag) .map { $0.isEmpty }
  • 51.
    passwordTextField.rx_text.asObservable() import RxSwift import RxCocoa .subscribeNext{ let buttonTitle = $0 ? "패스워드를 입력하세요" : "로그인 하기" self.loginButton?.setTitle(buttonTitle, forState: .Normal) } .addDisposableTo(disposeBag) .map { $0.isEmpty }
  • 53.
  • 55.
    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 “”
  • 56.
    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)
  • 57.
    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)
  • 60.
    import UIKit import RxSwift importRxCocoa 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() { … } }
  • 61.
    func addValidations() { letemailEmptyObservable = 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) }
  • 62.
  • 63.
    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 } } }
  • 64.
    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 } }
  • 65.
    extension Alamofire.Request { funcrx_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)) } }
  • 66.
    .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)) } }
  • 67.
    let collections =Variable([Collection]()) Router.Collections(["filter": filterParameter]).request .rx_JSON() .retry(1) .startWith([]) .catchErrorJustReturn([]) .bindTo(collections)
  • 68.
    let collections =Variable([Collection]()) Router.Collections(["filter": filterParameter]).request .rx_JSON() .retry(1) .startWith([]) .catchErrorJustReturn([]) .bindTo(collections) .retry(1)
  • 69.
    let collections =Variable([Collection]()) Router.Collections(["filter": filterParameter]).request .rx_JSON() .retry(1) .startWith([]) .catchErrorJustReturn([]) .bindTo(collections) .startWith([]) [ ] [Collection, …, Collection]
  • 70.
    let collections =Variable([Collection]()) Router.Collections(["filter": filterParameter]).request .rx_JSON() .retry(1) .startWith([]) .catchErrorJustReturn([]) .bindTo(collections) .catchErrorJustReturn([]) [ ]
  • 71.
    let collections =Variable([Collection]()) Router.Collections(["filter": filterParameter]).request .rx_JSON() .retry(1) .startWith([]) .catchErrorJustReturn([]) .bindTo(collections) .bindTo(collections) [ ] […] […] […]
  • 72.
  • 73.
  • 74.
  • 76.
    https://justhackem.wordpress.com/2015/03/19/rmvvm-architecture/ Reactive MVVM(Model-View-ViewModel) 모바일응용프로그램 아키텍쳐 https://github.com/devxoul/RxTodo RxTodo
  • 77.
  • 78.
  • 79.