KoreaMango ๋‚˜๋ฌด

[iOS] RxSwift - ์‘์šฉํ•ด๋ณด๊ธฐ ๋ณธ๋ฌธ

iOS/RxSwift

[iOS] RxSwift - ์‘์šฉํ•ด๋ณด๊ธฐ

KoreaMango 2022. 7. 23. 20:04

๐Ÿ™‰ RxSwift

โŒ Rx๊ฐ€ ์—†์ด UI ์„ค๊ณ„

ํ…์ŠคํŠธ ํ•„๋“œ ๋ธ๋ฆฌ๊ฒŒ์ดํŠธ๋ฅผ ์žก์Œ

ํ…์ŠคํŠธ ์ฒด์ธ์ง€ ์ด๋ฒคํŠธ๋ฅผ ๋ฐ›๊ณ 

ํŒจ์Šค์›Œ๋“œ๋‚˜ ์•„์ด๋”” ๋ถ„๊ธฐ๋ฅผ ์žก๊ณ 

valid ์ฒดํฌํ•˜๊ณ  true false๋ฅผ ํ†ตํ•ด hidden ์„ on off ํ•จ

๋‘˜๋‹ค valid ๋งž์œผ๋ฉด ๋ฒ„ํŠผ์„ hidden off

โญ•๏ธ Rx๋ฅผ ์‚ฌ์šฉํ•ด UI ์„ค๊ณ„

๋ฐ์ดํ„ฐ๊ฐ€ ํ•„์š”ํ•œ๋ฐ ๋‚˜์ค‘์— ๋ฐ์ดํ„ฐ๋ฅผ ์ค„ ๋•Œ ๋น„๋™๊ธฐ ์‚ฌ์šฉ

์•„์ด๋””๋ž‘ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๋ˆŒ๋ €์„ ๋•Œ ๊ฐ’์„ ๋‚˜์ค‘์— ์ฃผ๋‹ˆ๊นŒ ๋น„๋™๊ธฐ๋กœ ์ฒ˜๋ฆฌ๊ฐ€๋Šฅ

์ŠคํŠธ๋ฆผ์— ํด๋ฆญ์ด๋ฒคํŠธ, ํ…์ŠคํŠธ ์ฒด์ธ์ง€ ์ด๋ฒคํŠธ๋ฅผ ๋„ฃ๊ณ  ์„œ๋ธŒ์Šคํฌ๋ผ์ž…์— ์ „๋‹ฌ๋จ

UI ์ด๋ฒคํŠธ๊ฐ€ ์ฒ˜๋ฆฌ๋˜๋Š” ๊ฒƒ๋„ ๋น„๋™๊ธฐ๋กœ ์ฒ˜๋ฆฌ

โญ๏ธ ์‘์šฉ

์ฒซ ์ฝ”๋”ฉ

private func bindUI() {

	idField.rx.text
	//	.filter{ $0 != nil }
	//	.map{ $0! }
		.orEmpty
		.map(checkEmailValid)
		.subscribe(onNext: { b in 
			self.idValidView.isHidden = b
		})
		.disposed(by: disposeBag)

	pwField.rx.text
	//	.filter{ $0 != nil }   
	//	.map{ $0! }            
		.orEmpty
		.map(checkPasswordValid)
		.subscribe(onNext: { b in 
			self.pwValidView.isHidden = b
		})
		.disposed(by: disposeBag)
		

	Observable.combineLatest( // ๋‹ค์–‘ํ•œ ๋ฉ”์†Œ๋“œ๊ฐ€ ์žˆ๋‹ค. merge, zip ๋“ฑ๋“ฑ , ์ด๊ฒƒ๋“ค์ค‘ ์—ฌ๊ธฐ์— ์ œ์ผ ์–ด์šธ๋ฆฌ๋Š” ๊ฒƒ์€ combineLatest
		idField.rx.text.orEmpty.map(checkEmailValid),
		pwField.rx.text.orEmpty.map(checkPasswordVaild),
		resultSelector: { s1, s2 in s1 && s2 } // ๋จผ์ € ๋‚ด๋ ค์˜จ ์„ ์ฐฉ์ˆœ์œผ๋กœ s1, s2 ๊ฒฐ์ •
	)  
		.subscribe(onNext : { b in 
			self.loginButton.isEnabled = b
		})
		.disposed(by : disposeBag)
		
}

๋ฆฌํŒฉํ† ๋ง (์ค‘๋ณต ์ œ๊ฑฐ)

private func bindUI() {

	let idInputOb = idField.rx.text.orEmpty.asObservable() // ๋ช…ํ™•ํ•˜๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•œ asObservable()
	let pwInputOb  = pwField.rx.text.orEmpty.asObservable()

	let emailValidOb = idInputOb.map(checkEmailValid)
	let pwValidOb = pwInputOb.map(checkPasswordValid)

	idValidOb
		.subscribe(onNext : {b in self.idValidView.isHidden = b})
		.disposed(by: disposeBag)
	
	pwValidOb
		.subscribe(onNext : {b in self.idValidView.isHidden = b})
		.disposed(by: disposeBag)

	Observable.combineLatest(idValidOb, pwValidOb, resultSelector : {$0 && $1})
		.subscribe(onNext : {b in self.loginButton.isEnable = b})
		.disposed(by: disposeBag)

}

โญ๏ธ Subject

  • AsyncSubject
    • ๋์ด๋‚˜๋ฉด ๋งˆ์ง€๋ง‰์— ์žˆ๋Š” ๋ฐ์ดํ„ฐ๋งŒ ๋„˜๊ฒจ์ค€๋‹ค.
  • BehaviorSubject
    • ๋””ํดํŠธ ๊ฐ’์ด ์žˆ์Œ, ์ด ๊ฐ’์„ ๋„ฃ๊ณ  ์‹œ์ž‘ํ•จ
    • Subscribeํ•˜๋˜ ๋„์ค‘ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ๋‹ค๋ฅธ subscribe์—๋„ ์—๋Ÿฌ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ด
  • PublishSubject
    • ๋””ํดํŠธ ๊ฐ’์ด ์—†์Œ
  • ReplaySubject
    • ์ค‘๊ฐ„์— subscribe์„ ํ•˜๋ฉด ์ „์— ํ–ˆ๋˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค ๋„˜๊ฒจ์ฃผ๊ณ  ๊ทธ๋‹ค์Œ ๋„˜๊ฒจ์ค€๋‹ค.

subject ๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๋„ฃ์–ด์ค„ ์ˆ˜๋„ ์žˆ๊ณ  Subscribe ํ•ด์ค„ ์ˆ˜๋„ ์žˆ๋Š” ์นœ๊ตฌ๋‹ค.

// ํ•จ์ˆ˜ ๋ฐ–์— Subject ๋ฅผ ์ƒ์„ฑํ•จ

let idValid : BehaviorSubject<Bool> = BehaviorSubject(value: false)
let idInputText : BehaviorSubject<String> = BehaviorSubject(value: "")
let pwValid : BehaviorSubject<Bool> = BehaviorSubject(value: false)
let pwInputText : BehaviorSubject<String> = BehaviorSubject(value: "")

private func bindInput() {
/* ---------id---------- */

	// let idInputOb = idField.rx.text.orEmpty.asObservable() // ๋ช…ํ™•ํ•˜๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•œ asObservable()
	// let idValidOb = idInputOb.map(checkEmailValid)
	
	// Subject์— ๊ฐ’ ๋„ฃ์–ด์ฃผ๋Š” ๋ฒ• 1
	// idValidOb.subscribe(onNext : { b in 
	//	 self.idValid.onNext(b)
	// })

	// Subject์— ๊ฐ’ ๋„ฃ์–ด์ฃผ๋Š” ๋ฒ• 2
	// idValidOb.bind(to : idValid)

	// Subject ์ ์šฉ์‹œ - idText ๋‚ด๋ณด๋‚ด๊ธฐ 
	idField.rx.text.orEmpty
			.bind(to : idInputText)
			.disposed(by: disposeBag)

	// Subject ์ ์šฉ์‹œ - idValid ๋‚ด๋ณด๋‚ด๊ธฐ 
	idField.rx.text.orEmpty        // <- ์—ฌ๊ธฐ๋‹ค๊ฐ€ ์œ„์—์„œ idText๋ฅผ ๋‚ด๋ณด๋‚ธ subject ๋ฅผ ๋„ฃ์–ด์ค„ ์ˆ˜ ์žˆ๋‹ค. (idInputText)
			.map(checkEmailValid)      // ์ด๋ฉ”์ผ ์ฒดํฌํ•œ ๊ฒฐ๊ณผ๋ฅผ
			.bind(to: idValid)         // ๋ฐ–์— ์™ธ๋ถ€์— idValid๋ผ๊ณ  ํ•˜๋Š” ๋ฐ์ดํ„ฐ ์ŠคํŠธ๋ฆผ์— ๋‹ด์€๊ฒƒ์ž„.
			.disposed(by: disposeBag)

/* ---------pw---------- */

	// let pwInputOb  = pwField.rx.text.orEmpty.asObservable()
	// let pwValidOb = pwInputOb.map(checkPasswordValid)

	// Subject์— ๊ฐ’ ๋„ฃ์–ด์ฃผ๋Š” ๋ฒ• 1
	// pwValidOb.subscribe(onNext : { b in 
	//	 self.pwValid.onNext(b)
	// })

	// Subject์— ๊ฐ’ ๋„ฃ์–ด์ฃผ๋Š” ๋ฒ• 2
	// pwValidOb.bind(to : pwValid)

	// Subject ์ ์šฉ์‹œ - pwText ๋‚ด๋ณด๋‚ด๊ธฐ 
	pwField.rx.text.orEmpty
			.bind(to : idInputText)
			.disposed(by: disposeBag)

	// Subject ์ ์šฉ์‹œ - idValid ๋‚ด๋ณด๋‚ด๊ธฐ 
	pwField.rx.text.orEmpty        // <- ์—ฌ๊ธฐ๋‹ค๊ฐ€ ์œ„์—์„œ pwText๋ฅผ ๋‚ด๋ณด๋‚ธ subject ๋ฅผ ๋„ฃ์–ด์ค„ ์ˆ˜ ์žˆ๋‹ค. (pwInputText)
			.map(checkPasswordValid)      // ์ด๋ฉ”์ผ ์ฒดํฌํ•œ ๊ฒฐ๊ณผ๋ฅผ
			.bind(to: pwValid)         // ๋ฐ–์— ์™ธ๋ถ€์— idValid๋ผ๊ณ  ํ•˜๋Š” ๋ฐ์ดํ„ฐ ์ŠคํŠธ๋ฆผ์— ๋‹ด์€๊ฒƒ์ž„.
			.disposed(by: disposeBag)

}

private func bindOutput() {
	idValid
		.subscribe(onNext : {b in self.idValidView.isHidden = b})
		.disposed(by: disposeBag)
	
	pwValid
		.subscribe(onNext : {b in self.idValidView.isHidden = b})
		.disposed(by: disposeBag)

	Observable.combineLatest(idValid, pwValid, resultSelector : {$0 && $1})
		.subscribe(onNext : {b in self.loginButton.isEnable = b})
		.disposed(by: disposeBag)
}

input ๊ณผ output๊ฐ€ ๋ถ„๋ฆฌ๋  ์ˆ˜ ์žˆ์—ˆ๋˜ ์ด์œ ๋Š” subject๋ฅผ ํ†ตํ•ด์„œ ๋ฐ”๊นฅ์œผ๋กœ ๋„์ง‘์–ด ๋‚ผ ์ˆ˜ ์žˆ์—ˆ๊ธฐ ๋–„๋ฌธ์ด๋‹ค.

bind ๋ฅผ ๋ณด๋‹ˆ SwiftUI ์— Binding์ด๋ž‘ ์กฐ๊ธˆ ๋น„์Šทํ•œ๊ฑฐ ๊ฐ™๊ธฐ๋„…

Ref

https://www.youtube.com/watch?v=w5Qmie-GbiA&list=PL03rJBlpwTaAh5zfc8KWALc3ADgugJwjq&index=1

https://reactivex.io/documentation/ko/observable.html

'iOS > RxSwift' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

[iOS] RxSwift - ๊ธฐ๋ณธ ๋ฌธ๋ฒ• ์ •๋ฆฌ  (0) 2022.07.23