์ด๊ธ์ Getting Started With RxSwift and RxCocoa ( https://www.raywenderlich.com/138547/getting-started-with-rxswift-and-rxcocoa )์ ๋ํ ๋ฒ์ญ์ ๋๋ค. (๋ฒ์ญ ํ๋ฝ์ ๋ฐ์ ๊ฒ์ ์๋์ง๋ง, ๋์์ด ํ์ํ์ ๋ถ๋ค์ ์ํด์ ์ฌ๋ ค๋ด ๋๋ค. ๋ฌธ์ ๊ฐ ๋ ๊ฒฝ์ฐ ๋ฐ๋ก ์ญ์ ํ๊ฒ ์ต๋๋ค. ใ กใ )
์ฝ๋๊ฐ ์ฌ๋ฌ๋ถ์ด ์ํ๋ ๋๋ก ์ ํํ ๋์ํ๋ฉด(์ ์ ๊ณ ์์ด์๋ ๋ฌ๋ฆฌ) ๋งค์ฐ ๊ธฐ์ฉ๋๋ค. ํ๋ก๊ทธ๋จ์์ ์ด๋ค ๊ฒ์ ๋ณ๊ฒฝํ๊ณ ์ ๋ฐ์ดํธ ํ๋๋ก ์ด์ผ๊ธฐํ๋ฉด ๊ทธ๋๋ก ๋ฉ๋๋ค. ์ข์ ์ฝ๋์ ๋๋ค.
๊ฐ์ฒด์งํฅ ์๋์ ๋๋ถ๋ถ์ ํ๋ก๊ทธ๋๋ฐ์ ๋ช ๋ นํ(imperative)์ด์์ต๋๋ค. ์ด๋ฐ์์ด์์ฃ . ์ฝ๋๊ฐ ํ๋ก๊ทธ๋จ์๊ฒ ํด์ผํ ์ผ์ ๋งํ๊ณ , ๋ณ๊ฒฝ์ฌํญ์ ์ฌ๋ฌ๊ฐ์ง ๋ฐฉ๋ฒ์ผ๋ก ์ ๋ฌ๋ฉ๋๋ค. ๊ทธ๋ฌ๋ ๋ณดํต ๋ญ๊ฐ ๋ณ๊ฒฝ๋๋ฉด ์์คํ ์ ์ ๊ทน์ ์ผ๋ก ์๋ ค์ผ ํฉ๋๋ค.
๊ทธ๋ฐ๋๋ก ๊ด์ฐฎ์์ต๋๋ค. ํ์ง๋ง, ๋ง์ฝ ์ฌ๋ฌ๋ถ์ด ์ฑ์์ ๋ณ๊ฒฝ ์ฌํญ์ด ๋ฐ์ํ ๋ ์๋์ผ๋ก ์ฝ๋๊ฐ ์ ๋ฐ์ดํธํ๋๋ก ์ค์ ํ ์ ์์๋ค๋ฉด ๊ทธ๊ฒ๋ณด๋ค ์ข์ ์ ์์๊น์? ์ด๊ฒ์ด reactive programming ์ ๊ธฐ๋ณธ ์ฌ์์ ๋๋ค.: ์ฑ์ด ์ง์ ์ ์ผ๋ก ์ด๋ป๊ฒ ํ๋ผ๋ ์ฌ๋ฌ๋ถ์ ์ง์ ์์ด ๋ฐ์ดํฐ์ ๊ธฐ๋ฐํ์ฌ ๋ณ๊ฒฝ์ฌํญ์ ๋ฐ์ํ ์ ์์ต๋๋ค. ์ด๋ ๊ฒ ๋๋ฉด ํน์ ์ํ๋ฅผ ์ฒ๋ฆฌํ๋ ๊ฒ๋ณด๋ค ๋ฐ๋ก ์์ ๋ก์ง์ ์ง์คํ๊ธฐ ๋ ์ฌ์์ง๋๋ค.
์ด๋ฐ ๊ฒ์ ์์ Objective-C ์์๋ ๋ณดํต Key-Value Observation ( https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/KeyValueObserving/KeyValueObserving.html ) ์ผ๋ก ํ ์ ์๊ณ , Swift์์๋ didSet ํน์ setter๋ฅผ override ํด์ ํ ์ ์์ต๋๋ค. ํ์ง๋ง ๋๋๋ก ์ด๋ฐ ๋ฉ์๋๋ค์ ์ ๋๋ก ๋ค๋ฃจ๊ธฐ๊ฐ ํ๋ค ์ ์์ต๋๋ค. Objective-C ์ Swift ์๋ ์ด๋ฐ ๋ฌธ์ ๋ฅผ ํผํด reactive programming ์ ๋ ์ฝ๊ฒ ์ฌ์ฉํ ์ ์๋๋ก ํ๋ ์ฌ๋ฌ framework๋ค์ด ์กด์ฌํฉ๋๋ค.
์ฃผ์: ์ข ๋ ์์ธํ ์๊ณ ์ถ๋ค๋ฉด, Rui Peres๊ฐ ์์ฑํ ์ฃผ์ framework ๋ค๊ฐ์ ์ฐจ์ด์ ์ ์ค๋ช ํ ์ข์ ๋ฌธ์( https://www.raywenderlich.com/126522/reactivecocoa-vs-rxswift )๋ฅผ ๋ณด์ธ์. ๋๊ธ ์์ญ์ ์ด์ ์ ์ธ ์ฌ๋๋ค์ด ์ด๋ค framework์ด ๋ ์ข๋ค๊ณ ์๊ฐํ๋ ์ง์ ๋ํ ์ฌ๋ฏธ์๋ ๊ด์ ๋ํ ์์ต๋๋ค.
์ค๋ ์ฌ๋ฌ๋ฒ์ ์ด์ฝ๋ฆฟ ๊ตฌ๋งค ์ฑ์ ์ฑ๊ฐ์ ๋ช ๋ นํ(imperative)์์ ๋ฉ์ง ๋ฐ์ํ(reactive)์ผ๋ก ํ๋ฐ๊ฟ์ํค๊ธฐ ์ํด์ ์ด๋ฐ framework๋ค ์ค ํ๋๋ฅผ ์ฌ์ฉํ๊ฒ ๋ ๊ฒ๋๋ค. RxSwift, ๊ทธ๋ฆฌ๊ณ ๊ทธ์ ๋๋ฃ RxCocoa๋ฅผ ์ฌ์ฉํ๊ฒ ๋ ๊ฒ๋๋ค.
RxSwift ์ RxCocoa๋ ReactiveX(๋ณดํต Rx๋ผ๊ณ ์ถ์ฝ๋ฉ๋๋ค.) ์ธ์ด ๋๊ตฌ๋ค ์ suite ์ ์ผ๋ถ์ ๋๋ค. ReactiveX๋ ์ฌ๋ฌ ํ๋ก๊ทธ๋๋ฐ ์ธ์ด์ ํ๋ซํผ๋ฅผ ์ง์ํฉ๋๋ค. ReactiveX๊ฐ .NET/C# ์ํ๊ณ์ ์ผ๋ถ๋ก์ ์์ํ๊ธฐ๋ ํ์ง๋ง, ๋ฃจ๋น ๊ฐ๋ฐ์, ์๋ฐ์คํฌ๋ฆฝํธ ๊ฐ๋ฐ์, ํนํ ์๋ฐ์ ์๋๋ก์ด๋ ๊ฐ๋ฐ์๋ค ์ฌ์ด์์ ํญ๋ฐ์ ์ธ ์ธ๊ธฐ๋ฅผ ์ป์์ต๋๋ค.
RxSwift๋ Swift ํ๋ก๊ทธ๋๋ฐ ์ธ์ด์ ์๋ํ๋ framework์ ๋๋ค. ๋ฐ๋ฉด์ RxCocoa๋ iOS์ OSX์์ ์ฌ์ฉ๋๋ Cocoa API๋ฅผ reactive ํ ํฌ๋๋ค๋ก ์ฌ์ฉํ๊ธฐ ์ฝ๊ฒ ๋์์ ์ฃผ๋ framework์ ๋๋ค.
ReactiveX framework๋ ๊ณตํต ์ฉ์ด๋ฅผ ์ ๊ณตํจ์ผ๋ก์จ ๋ค์ํ ํ๋ก๊ทธ๋๋ฐ ์ธ์ด์์ ๋ฐ๋ณต์ ์ผ๋ก ์ฌ์ฉ๋๋ ํน์ ์์ ๋ค์ ์ ์ฉํ๊ฒ ์ค๊ณ๋์ด ์์ต๋๋ค. (์ด๋ก ์ ์ผ๋ก) ์ด๊ฒ์ ์๋ก์ด ์ธ์ด์ ๊ณตํต ์์ ์ ์ด๋ป๊ฒ ๋งคํํ ์ง ์์ ๋ด๋๋ฐ ์๊ฐ์ ๋ญ๋นํ๊ธฐ ๋ณด๋ค๋ ์ธ์ด ๊ทธ์์ฒด์ ๋ฌธ๋ฒ์ ๋ ์ง์คํ๊ธฐ ์ฝ๊ฒ ๋ง๋ค์ด ์ค๋๋ค.
์ด ํํฐ๋ฆฌ์ผ์ ์ํด ์์์ผ ํ ๋๊ฐ์ง ๊ฐ๋ ์ Observable ๊ณผ Observer ์ ๋๋ค.
- Observable ๋ ๋ณ๊ฒฝ ์๋ฆผ์ ๋ฐ์์ํค๋(emit) ์ด๋ค ๊ฒ์ ๋๋ค.
- Observer๋ ๋ณ๊ฒฝ ์ฌํญ์ด ๋ฐ์ํ์ ๋ ์๋ฆผ์ ๋ฐ๊ธฐ ์ํด์ Observable์ ๋ํด์ ๊ตฌ๋ (subscribe)ํ๋ ์ด๋ค ๊ฒ์ ๋๋ค.
ํ๋์ Observable์ ์ฌ๋ฌ Observer๋ค์ด listen(subscribe)ํ ์ ์์ต๋๋ค. ์ด๊ฒ์ Observable ์ด ๋ณ๊ฒฝ๋ ๋ ๊ทธ๊ฒ์ ๋ชจ๋ Observer๋ค์๊ฒ ์๋ฆผ์ด ์ ๋ฌ๋๋ค๋ ๊ฒ์ ์๋ฏธํฉ๋๋ค.
RxSwift ์ RxCocoa๋ ARC์ ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ๋ฅผ ๋ค๋ฃฐ ์ถ๊ฐ์ ์ธ ํด์ ๊ฐ์ง๊ณ ์์ต๋๋ค. DisposeBag ์ ๋๋ค. ์ด๊ฒ์ Observer ๊ฐ์ฒด๋ค์ ๋ํ ๊ฐ์์ ๊ฐ๋ฐฉ("bag") ์ ๋๋ค. DisposeBag์ ์๋ Observer๋ค์ ๋ถ๋ชจ ๊ฐ์ฒด๊ฐ deallocte๋ ๋, ํ๊ธฐ๋ฉ๋๋ค.
DisposeBag์ property๋ก ๊ฐ์ง๊ณ ์๋ ๊ฐ์ฒด์ ๋ํด์ deinit() ์ด ํธ์ถ๋ ๋, ๊ทธ ๊ฐ๋ฐฉ(DisposeBag)์ ๋น์์ง๋๋ค. ๊ทธ๋ฆฌ๊ณ ํ๊ธฐ๋๋ Observer๋ ์๋์ผ๋ก ๊ด์ฐฐํ๊ณ (observe) ์๋ ๊ฒ์ผ๋ก ๋ถํฐ ํด์ง(unsubscribe)๋ฉ๋๋ค. ์ด๊ฒ์ ๋ณดํต ๊ทธ๋ ๋ฏ์ด ARC๊ฐ ๋ฉ๋ชจ๋ฆฌ๋ฅผ ํด์ ํ ์ ์๊ฒ ํด์ค๋๋ค.(reference count๊ฐ ์ค์ด์ 0์ด ๋๊ณ ARC๊ฐ ํด์ ํ ์ ์๊ฒ ๋ฉ๋๋ค.)
DisposeBag์ด ์๋ค๋ฉด, ๋๊ฐ์ง ์ค ํ๋์ ๊ฒฐ๊ณผ๋ฅผ ์ป๊ฒ ๋ ๊ฒ๋๋ค.:Observer๊ฐ retain cycle์ ๋ง๋ค๊ฒ ๋๊ณ , ๋ฌดํ์ ๊ด์ฐฐํ๊ฒ ๋๊ฑฐ๋, ์ฌ๋ฌ๋ถ์ ๊ฐ์ฒด์์ deallocted ๋์, crash๋ฅผ ์ ๋ฐํ๊ฒ ๋ฉ๋๋ค.
๊ทธ๋์ ์ข์ ARC ์๋ฏผ์ด ๋๊ธฐ ์ํด์๋ Observable ๊ฐ์ฒด๋ค์ ์์ฑํ ๋, DisposeBag์ ์ถ๊ฐํ๋ ๊ฒ์ ๊ธฐ์ตํ์ธ์. ๊ทธ๋์ผ Observable ๊ฐ์ฒด๋ค์ด ๊น๋ํ๊ฒ ์ ๋ฆฌ๋ ๊ฒ๋๋ค.
์ด์ฝ๋ฆฟ์ ๋จน์ผ๋ฌ ๊ฐ์๋ค! ์ด ํํฐ๋ฆฌ์ผ์ ์ํ starter ์ฑ์ธ Chocotasic ์ ์ฌ๊ธฐ( https://koenig-media.raywenderlich.com/uploads/2016/10/Chocotastic-starter-s3-rxs-3b1.zip )์์ ๋ฐ์ผ์ค ์ ์์ต๋๋ค.
zip ํ์ผ์ ๋ค์ด๋ก๋ ๋ฐ๊ณ Xcode์์ project๋ฅผ ์ฌ์ธ์.
์ฃผ์:์ด ํ๋ก์ ํธ๋ CocoaPods๋ฅผ ์ด์ฉํฉ๋๋ค. ๊ทธ๋์ Xcode์์ Chocotasic.xcworkspace ํ์ผ์ Xcode์์ ์ด์ด์ผ ํฉ๋๋ค.
์ฑ์ ๋น๋ํ๊ณ ์คํ์ํค์ธ์. ๋ง์นจ๋ด ์ฌ๋ฌ๋ถ์ ๋ค์์ ํ๋ฉด์ ๋ณด๊ฒ ๋ ๊ฒ๋๋ค. ๊ฐ๋ณ ๊ฐ๊ฒฉ๊ณผ ํจ๊ป ์ ๋ฝ์์ ์ด ์ ์๋ ๋ช๊ฐ์ง ์ด์ฝ๋ฆฟ ์ข ๋ฅ๋ค์ด ๋ฆฌ์คํธ๋ก ๋ณด์ฌ์ง๋๋ค.
์ด์ฝ๋ฆฟ ์ ํญ์ ํ๋ฉด ์นดํธ์ ๊ทธ ์ ํ์ด ์ถ๊ฐ ๋ ๊ฒ๋๋ค.
์ฐ์ธก ์๋จ์ ์๋ ์นดํธ๋ฅผ ํญํ๋ฉด ์ฒดํฌ ์์ํ๊ฑฐ๋ cart๋ฅผ resetํ ์ ์๋ ํ์ด์ง๋ก ์ด๋๋ฉ๋๋ค.
์ฒดํฌ ์์์ ์ ํํ๋ฉด, ์ ์ฉ์นด๋ ์ ๋ ฅ ํผ์ด ๋ํ๋ ๊ฒ๋๋ค.
ํํฐ๋ฆฌ์ผ์์ ๋์ค์, ์์ reactive programming์ ์ฌ์ฉํ๋๋ก ์ค์ ํ๊ธฐ ์ํด์ ๋ค์ ๋์ ์ฌ๊ฒ๋๋ค. cart ์์ฝ ํ์ด์ง๋ก ๋์๊ฐ๋ ค๋ฉด Cart ๋ฒํผ์ ํญํ์ธ์. ๊ทธ๋ฐ๋ค์ ๋น cart ๋ฅผ ๊ฐ์ง ๋ฉ์ธ ๋ฉ์ด์ง๋ก ๋์ ๊ฐ๋ ค๋ฉด Reset ๋ฒํผ์ ํญํ์ธ์.
์ด์ ๊น์ง ์ฌ๋ฌ๋ถ์ ์ ํ๋ฆฌ์ผ์ด์ ์ด ์ด๋ค์ง ๋ดค์ต๋๋ค. ์ด์ ๋ ์ด๋ป๊ฒ ๋์ํ๋์ง ์กฐ์ฌํด๋ณผ ์๊ฐ์ ๋๋ค. ChocolatesOfTheWorldViewController.swift๋ฅผ ์ฌ์ธ์. ์ฌ๋ฌ๋ถ์ ๊ฝค ์ ํ์ ์ธ UITableViewDelegate์ UITableViewDataSource๋ฅผ extension๋ค์ ๋ณด๊ฒ ๋ ๊ฒ๋๋ค.
updateCartButton() ๋ฉ์๋๊ฐ ๋ํ ์์ต๋๋ค. ์ด ๋ฉ์๋๋ cart button์ ์นด๋์ ์๋ ์ด์ฝ๋ฆฟ์ ๊ฐ์๋ก ์ ๋ฐ์ดํธ ํฉ๋๋ค. ์ด ๋ฉ์๋๋ ๋ ๊ณณ์์ ํธ์ถ๋ฉ๋๋ค. view controller๊ฐ ๋ณด์ฌ์ง ๋ ํธ์ถ๋๋ viewWillAppear(:) ์ ์นดํธ์ ์๋ก์ด ์ด์ฝ๋ฆฟ์ด ์ถ๊ฐ๋ ํ์ ํธ์ถ๋๋ tableView):didSelectRowAt:)์์ ํธ์ถ๋ฉ๋๋ค.
์ด๊ฒ๋ค์ ๋๋ค ๋ช ๋ นํ(imperative)์ธ ๋ฐฉ๋ฒ์ ๋๋ค.:๋ช ์์ ์ผ๋ก ๊ฐ์๋ฅผ ์ ๋ฐ์ดํธํ๋ ๋ฉ์๋๋ฅผ ํธ์ถํด์ผ ํฉ๋๋ค. ์ง๊ธ์ ์ด๋์์ ๊ฐ์ ๋ณ๊ฒฝํ๋์ง ๊ธฐ์ตํด๋ฌ์ผ ํ์ง๋ง, ๋ฐ์ํ(reactive) ๊ธฐ์ ์ ์ฌ์ฉํด์ ์ฝ๋๋ฅผ ์ฌ์์ฑํ ๊ฒ๋๋ค. ๊ทธ๋ ๊ฒ ํ๋ฉด, ๊ฐ์๊ฐ ์ด๋์์ ์ด๋ป๊ฒ ๋ณ๊ฒฝ๋๋์ง ์๊ด์์ด ๋ฒํผ์ด ์ ๋ฐ์ดํธ ๋ ๊ฒ๋๋ค.
์นด๋์ item๋ค์ ์ฐธ์กฐํ๋ ๋ชจ๋ ๋ฉ์๋ค์ ShoppingCart.sharedCart ์ฑ๊ธํค(SingleTon)์ ์ฌ์ฉํฉ๋๋ค. ShoppingCart.swift๋ฅผ ์ฌ์ธ์. ๊ทธ๋ฌ๋ฉด ์ธ์คํด์ค ๊ฐ์ ์ค์ ํ๋ ๊ฝค ์ ํ์ ์ธ ์ฑํดํด(SingleTon)์ ๋ณด๊ฒ ๋ ๊ฒ๋๋ค.
var chocolates = [Chocolate]()
๋น์ฅ์, chocolates ๋ณ์์ ๋ด์ฉ์ ๋ํ ๋ณ๊ฒฝ์ฌํญ์ ๊ด์ฐฐ(observe)ํ ์ ์์ต๋๋ค. ์ ์์ didSet ๋ฅผ ์ถ๊ฐํ ์ ์์ง๋ง, ๋ฐฐ์ด์ ๊ฐ ์์๊ฐ ์๋ ์ ์ฒด ๋ฐฐ์ด์ด ๋ณ๊ฒฝ๋์์ ๋๋ง ํธ์ถ๋ ๊ฒ๋๋ค.
๋คํ์ค๋ฝ๊ฒ๋, RxSwift๋ ํด๋ต์ ๊ฐ์ง๊ณ ์์ต๋๋ค. chocolates ๋ณ์๋ฅผ ์์ฑํ๋ ์ค์ ์ด๋ ๊ฒ ๋ณ๊ฒฝํ์ธ์.
let chocolates: Variable<[Chocolate]> = Variable([])
์ฃผ์: ์ด ๋ณ๊ฒฝ์ ์ฌ์ด๋๋ฐ์ ์๋นํ ์ค๋ฅ๋ค์ ์ ๋ฐํ ๊ฒ๋๋ค. ํ์ง๋ง ๊ธ๋ฐฉ ์์ ํ ์ ์์ ๊ฒ๋๋ค.
์ด ๋ฌธ๋ฒ์ ์กฐ๊ธ ์ด๋ ค์์ ๋จธ๋ฆฌ๋ฅผ ๊ฐ์ธ๊ฒ ํ ์๋ ์์ต๋๋ค. ๊ทธ๋๋ ์ด๋ฉด์์ ์ด๋ค ๊ฒ๋ค์ด ์ด๋ฃจ์ด์ง๋ ๊ฒ์ ์ดํดํ๋๋ฐ ๋์์ด ๋ฉ๋๋ค.
chocolates ๋ฅผ Chocolate ๊ฐ์ฒด๋ค์ Swift ๋ฐฐ์ด๋ก ์ค์ ํ๊ธฐ ๋ณด๋ค๋, Chocolate ๊ฐ์ฒด๋ค์ Swift ๋ฐฐ์ด์ ํ์ ์ ๊ฐ์ง๋ RxSwift Variable ๋ก ์ ์ํ์ต๋๋ค.
Variable๋ ํด๋์ค์ด๊ณ , ๊ทธ๋์ reference ๋ฌธ๋ฒ์ ์ฌ์ฉํฉ๋๋ค. - chocolates ๊ฐ Variable ์ธ์คํด์ค๋ฅผ ์ฐธ์กฐํ๋ค๋ ๊ฒ์ ์๋ฏธํฉ๋๋ค.
Variable ๋ value๋ผ๋ property๋ฅผ ๊ฐ์ง๋๋ค. ์ด๊ฒ์ Chocolate ๊ฐ์ฒด๋ค์๋ํ ๋ฐฐ์ด์ด ์ ์ฅ๋๋ ๊ณณ์ ๋๋ค.
Variable์ ๋ง์ ์ asObservable()์ด๋ผ๊ณ ๋ถ๋ฆฌ๋ ๋ฉ์๋๋ก ๋ถํฐ ์ต๋๋ค. ๋งค๋ฒ ์๋์ผ๋ก value ๋ฅผ ํ์ธํ๋ ๋์ ์, ์ฌ๋ฌ๋ถ ๋์ ์ ๊ณ์ ์ง์ผ๋ด์ค Observer๋ฅผ ์ถ๊ฐํ ์ ์์ต๋๋ค. ๊ฐ์ด ๋ณ๊ฒฝ๋ ๋, Observer๊ฐ ์ฌ๋ฌ๋ถ์๊ฒ ์๋ ค์ค๋๋ค. ๊ทธ๋์ ๋ณ๊ฒฝ์ ๋ฐ์ํ ์ ์์ต๋๋ค.
์ด ์ค์ ์์ ๋จ์ ์ chocolates ๋ฐฐ์ด์ ์ด๋ค ๊ฒ์ ์ ๊ทผํ๊ฑฐ๋ ๋ณ๊ฒฝํ ํ์๊ฐ ์์ ๋, ์ง์ ์ ์ผ๋ก ํ์ง ๋ชปํ๊ณ , value property๋ฅผ ํตํด์ ํด์ผ ํ๋ ๊ฒ๋๋ค. ์ด๊ฒ์ด ์ปดํ์ผ๋ฌ๊ฐ ์๋ฌ๋ฅผ ๋ด๋ฑ๋ ์ด์ ์ ๋๋ค. ์ด์ ๊ทธ ์ค๋ฅ๋ค์ ์์ ํ ์๊ฐ์ ๋๋ค.!
ShoppingCart.swift์์ totalCost()๋ฉ์๋๋ฅผ ์ฐพ๊ณ
return chocolates.reduce(0) {
์์
return chocolates.value.reduce(0) {
์ผ๋ก ๋ณ๊ฒฝํ์ธ์.
itemCountString()์์
guard chocolates.count > 0 else {
์์
guard chocolates.value.count > 0 else {
์ผ๋ก ๋ณ๊ฒฝํ์ธ์.
๊ทธ๋ฆฌ๊ณ
let setOfChocolates = Set<Chocolate>(chocolates)
์์
let setOfChocolates = Set<Chocolate>(chocolates.value)
์ผ๋ก ๋ณ๊ฒฝํ์ธ์.
๋ง์ง๋ง์ผ๋ก
let count: Int = chocolates.reduce(0) {
์์
let count: Int = chocolates.value.reduce(0) {
์ผ๋ก ๋ณ๊ฒฝํ์ธ์.
CartViewController.swift์์ reset()์ ์ฐพ๊ณ ,
ShoppingCart.sharedCart.chocolates = []
์์
ShoppingCart.sharedCart.chocolates.value = []
์ผ๋ก ๋ณ๊ฒฝํ์ธ์.
ChocolatesOfTheWorldViewController.swift ์์ ๋ค์, updateCartButton()์ ๊ตฌํ์
cartButton.title = "\(ShoppingCart.sharedCart.chocolates.value.count) \u{1f36b}"
์ผ๋ก ๋ณ๊ฒฝํ์ธ์.
๊ทธ๋ฆฌ๊ณ tableView(_:didSelectRowAt:)์์
ShoppingCart.sharedCart.chocolates.append(chocolate)
์์
ShoppingCart.sharedCart.chocolates.value.append(chocolate)
์ผ๋ก ๋ณ๊ฒฝํ์ธ์.
ํด, ๋ง์นจ๋ด, Xcode ๊ฐ ํ๋ณตํ ๊ฒ๋๋ค. ๊ทธ๋ฆฌ๊ณ ์๋ฌ๊ฐ ์์ ๊ฒ๋๋ค. ์ด์ chocolates๊ฐ ๊ด์ฐฐ๋ (observed) ์ ์๋ค๋ ์ด์ ์ ์ป์ ์ ์์ต๋๋ค!
ChocolatesOfTheWorldViewController.swift ๋ก ๊ฐ์ธ์. ๊ทธ๋ฆฌ๊ณ property๋ค์ ๋ฆฌ์คํธ์ ๋ค์์ ์ถ๊ฐ ํ์ธ์.
let disposeBag = DisposeBag()
์ด ์ฝ๋๋ DisposeBag์ ์์ฑํฉ๋๋ค. DisposeBag์ deinit()๊ฐ ํธ์ถ๋ ๋, ์ฌ๋ฌ๋ถ์ด ์ค์ ํ Observer๋ค์ด ์ ๋ฆฌ๋๋๋ก(clean up)ํ๊ธฐ ์ํ ๊ฒ๋๋ค.
//MARK: Rx Setup ์ฃผ์ ์๋์ ๋ค์์ ์ฝ๋๋ฅผ ์ถ๊ฐ ํ์ธ์.
//MARK: Rx Setup
private func setupCartObserver() {
//1
ShoppingCart.sharedCart.chocolates.asObservable()
.subscribe(onNext: { //2
chocolates in
self.cartButton.title = "\(chocolates.count) \u{1f36b}"
})
.addDisposableTo(disposeBag) //3
}
์ด ์ฝ๋๋ cart๋ฅผ ์๋์ผ๋ก ๊ฐฑ์ ํ๊ธฐ ์ํด์ reactive Observer๋ฅผ ์ค์ ํฉ๋๋ค. ๋ณด์๋ค ์ํผ, RxSwift๋ chained function๋ค์ ๋ง์ด ์ฌ์ฉํฉ๋๋ค. chained function๋ค์ ๊ฐ๊ฐ์ ํจ์๋ค์ด ์ด์ ํจ์์ ๊ฒฐ๊ณผ๋ฅผ ์ด์ฉํ๋ค๋ ๊ฒ์ ์๋ฏธํฉ๋๋ค.
์ด ๊ฒฝ์ฐ์ ์ด๋ป๊ฒ ๋ฐ์ํ๋์ง ์ค๋ช ํ๋ฉด,
1.๋จผ์ , shopping cart์ chocolates ๋ณ์๋ฅผ Observable๋ก ์ก์ต๋๋ค.
2.Observable์ value ์ ๋ณ๊ฒฝ์ฌํญ์ด ์๋์ง ์๊ธฐ ์ํด์ ํด๋น Observable์ ๋ํด์ subscribe(onNext:)๋ฅผ ํธ์ถํฉ๋๋ค. subscribe(onNext:)๋ ๊ฐ๋ค์ด ๋ณ๊ฒฝ๋ ๋ ๋ง๋ค ์คํ๋ ํด๋ก์ ๋ฅผ ๋ฐ์ต๋๋ค. ํด๋ก์ ์ ์ ๋ ฅ ํ๋ผ๋ฏธํฐ๋ Observable์ ์๋ก์ด ๊ฐ์ ๋๋ค. ๊ทธ๋ฆฌ๊ณ unsubscribeํ๊ฑฐ๋ subscribe์ด dispose๋ ๋๊น์ง ์ด๋ฐ ์๋ฆผ์ ๊ณ์ ๋ฐ์ ๊ฒ์ ๋๋ค. ์ด ๋ฉ์๋๋ก ๋ถํฐ ๋๋ ค ๋ฐ๋ ๊ฒ์ Disposable์ ๋ถํฉํ๋ Observer์ ๋๋ค.
3.์ด์ ๋จ๊ณ๋ก ๋ถํฐ ์ป์ Observer๋ฅผ ๊ตฌ๋ ํ๊ณ ์๋(subscribing) ๊ฐ์ฒด๊ฐ deallocte๋ ๋ subscribe๊ฐ dispose๋๋๋ก disposeBag์ ์ถ๊ฐํฉ๋๋ค.
๋ง์ง๋ง์ผ๋ก ๋ช ๋ นํ์ updateCartButton() ๋ฉ์๋๋ฅผ ์ ๊ฑฐํ์ธ์. ์ด๊ฒ์ viewWillAppear(:) ์ tableView(:didSelectRowAt:)์ ์ค๋ฅ๋ฅผ ์ ๋ฐํ ๊ฒ๋๋ค.
์ค๋ฅ๋ค์ ์์ ํ๊ธฐ ์ํด์, ์ ์ฒด viewWillAppear(:)๋ฅผ ์ ๊ฑฐํ์ธ์.(super๋ฅผ ํธ์ถํ๋ ๊ฒ ์ด์ธ์๋ updateCartButton()๋ฅผ ํธ์ถํ๋ ๊ฒ์ด ์ ๋ถ์ด๊ธฐ ๋๋ฌธ์) ๊ทธ๋ฆฌ๊ณ ๋์ tableView(:didSelectRowAt)์์ updateCartButton ํธ์ถ์ ์ ๊ฑฐํ์ธ์.
๋น๋ํ๊ณ ์คํํ์ธ์. ์ด์ฝ๋ฆฟ ๋ฆฌ์คํธ๊ฐ ๋ณด์ผ ๊ฒ๋๋ค.
ํ์ง๋ง cart๋ฅผ ์ํ ๋ฒํผ์ด ๋จ์ง "item"์ด๋ผ๊ณ ๋ง ํ์๋๋ ์๋ค๋ ๊ฒ์ ์์ธ์. ๊ทธ๋ฆฌ๊ณ ์ด์ฝ๋ฆฟ๋ค์ ๋ฆฌ์คํธ๋ฅผ ํญ์ ํด๋ ์๋ฌด์ผ๋ ์ผ์ด๋์ง ์์ต๋๋ค. ๋ญ๊ฐ ์๋ชป๋ ๊ฑธ๊น์?
Rx Observer๋ค์ ์ค์ ํ๋ ํจ์๋ฅผ ๋ง๋ค์์ต๋๋ค. ํ์ง๋ง, ์ง๊ธ์ ๊ทธ ํจ์๋ฅผ ์ค์ ๋ก ํธ์ถํ๋ ๋ถ๋ถ์ด ์์ต๋๋ค. ๊ทธ๋์ Observer๋ค์ด ์ค์ ๋์ง ์์ต๋๋ค. ์ด ์ค๋ฅ๋ฅผ ์์ ํ๊ธฐ ์ํด์, viewDidLoad():์ ๋ค์์ ์ฝ๋๋ฅผ ์ถ๊ฐํ์ธ์.
setupCartObserver()
์ด์ฝ๋ฆฟ ๋ฆฌ์คํธ๋ฅผ ๋ค์ ๋ณด๊ธฐ ์ํด์ ์ฑ์ ๋น๋ํ๊ณ ์คํํ์ธ์.
๋ช๊ฐ์ ์ด์ฝ๋ฆฟ์ ํญ์ ํ์ธ์.-item๋ค์ ์ซ์๊ฐ ์๋์ผ๋ก ์
๋ฐ์ดํธ ๋ฉ๋๋ค!
์ฑ๊ณต!๋ชจ๋ ์ด์ฝ๋ฆฟ์ ์นดํธ์ ๋ด์ ์ ์์ต๋๋ค.
์ง๊ธ ์ฐ๋ฆฌ๋ RxSwift๋ฅผ ์ด์ฉํด์ ์นดํธ๋ฅผ reactiveํ๊ฒ ๋ง๋ค์์ต๋๋ค. UITableView๋ฅผ ์ญ์ reactiveํ๊ฒ ๋ง๋ค๊ธฐ ์ํด์ RxCocoa๋ฅผ ์ฌ์ฉํ ๊ฒ๋๋ค.
RxCocoa๋ ์ฌ๋ฌ ๋ค๋ฅธ ์ ํ์ UI ์์๋ค์ ์ํ reactive API๋ค์ ๊ฐ์ง๋๋ค. ์ด๊ฒ๋ค์ override delegate ํน์ data source ๋ฉ์๋๋ค์์ด ๋ฐ๋ก UITableView๋ฅผ ๊ฐ์ ๊ฒ์ ์ค์ ํ ์ ์๋ ์ต์ ์ ์ ๊ณตํฉ๋๋ค.
์ด๋ฐ ๊ฒ๋ค์ด ์ด๋ป๊ฒ ๋์ํ๋์ง ๋ณด์ฌ์ฃผ๊ธฐ ์ํด์, ์ ์ฒด UITableViewDataSource์ UITableViewDelegate extension๋ค๊ณผ ๊ทธ๊ฒ๋ค์ ๋ชจ๋ ๋ฉ์๋๋ค์ ์ง์ฐ์ธ์. ๋ค์์ผ๋ก, viewDidLoad() ์ ์๋ tableView.dataSource ์ tableView.delegate์ ๋ํ ํ ๋น์ ์ง์ฐ์ธ์.
์ฑ์ ๋น๋ํ๊ณ ์คํํ์ธ์. ๊ทธ๋ฌ๋ฉด ์ด์ฝ๋ฆฟ๋ค์ด ๋ชจ๋ ๊ฐ์๊ธฐ ์ฌ๋ผ์ ธ์ ๊ฝค ์ฌํ๊ณ ํ
๋น์ด ์๋ tableView๋ฅผ ๋ณด๊ฒ ๋ ๊ฒ๋๋ค.
์ฌ๋ฏธ ์๋ค์. ์ด์ฝ๋ฆฟ๋ค์ ๋ณต์ํ ์๊ฐ์ ๋๋ค!
๋จผ์ , reactive tableView๋ฅผ ๊ฐ์ง๊ธฐ ์ํด์, tableView๊ฐ ๋ฐ์ํ ๋ฌด์์ด ํ์ํฉ๋๋ค. ChocolatesOfTheWorldViewController.swift ์์ europeanChocolates property๋ฅผ Observable๋ก ๊ฐฑ์ ํ์ธ์.
let europeanChocolates = Observable.just(Chocolate.ofEurope)
just(_:) ๋ฉ์๋๋ Observable์ ๊ธฐ์ด๊ฐ ๋๋ value ์ ๋ํ ๋ณ๊ฒฝ์ด ์ค์ ๋ก ์์ง๋ง, Observable value๋ก์ ์ ๊ทผํ ์ ์๋ค๋ ๊ฒ์ ๋ํ๋ ๋๋ค.
์ฃผ์: ๋๋๋ก, just(_:)๋ฅผ ํธ์ถํ๋ ๊ฒ์ Reactive Programming์ด ๊ณผ๋ํ ์ ์๋ค๋ ํ์์ ๋๋ค. ๊ฒฐ๊ตญ ๊ฐ์ด ๋ณ๊ฒฝ ๋์ง ์๋๋ค๋ฉด, ๊ฐ์ ๋ฐ์ํ๋๋ก ์ค๊ณ๋ ํ๋ก๊ทธ๋๋ฐ ์คํฌ์ ์ ์จ์ผ ํ ๊น์? ์ด ์์ ์์๋, ๋์ค์ ๋ณ๊ฒฝ๋ view cell๋ค์ ๋ฐ์์ ์ค์ ํ๊ธฐ ์ํด์ ์ฌ์ฉํ๊ณ ์์ต๋๋ค. ํ์ง๋ง Rx๋ฅผ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ์ฃผ์๊น๊ฒ ์ดํด๋ณด๋ ๊ฒ์ ์ข์ ์๊ฐ์ ๋๋ค. ๋ง์น๋ฅผ ๊ฐ์ง๊ณ ์๋ค๊ณ ๋ชจ๋ ๋ฌธ์ ๊ฐ ๋ชป์ด๋ผ๋ ๊ฒ์ ์๋ฏธํ๋ ๊ฒ์ ์๋๊ธฐ ๋๋ฌธ์ ๋๋ค.:]
์ด์ europeanChocolates์ Observable๋ก ๋ง๋ค์์ต๋๋ค. ๋ค์์ ์ถ๊ฐ ํ์ธ์.
private func setupCellConfiguration() {
//1
europeanChocolates
.bindTo(tableView
.rx //2
.items(cellIdentifier: ChocolateCell.Identifier,
cellType: ChocolateCell.self)) { // 3
row, chocolate, cell in
cell.configureWithChocolate(chocolate: chocolate) //4
}
.addDisposableTo(disposeBag) //5
}
์ฌ๊ธฐ์ ์ผ์ด๋๋ ์ผ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
1.tableView ์ ๊ฐ๊ฐ์ row์ ๋ํด์ ์คํ๋์ด์ผ ํ ์ฝ๋์ europeanChocolates Observable์ ์ฐ๊ณ์ํค๊ธฐ ์ํด์ bindTo(_:)๋ฅผ ํธ์ถํฉ๋๋ค.
2.ํธ์ถํ๋ ํด๋์ค๊ฐ ๋ฌด์์ด๋ ๊ฐ์ rx๋ฅผ ํธ์ถํจ์ผ๋ก์จ RxCocoa extension๋ค์ ์ ๊ทผํ ์ ์์ต๋๋ค. -์ด ๊ฒฝ์ฐ์๋, UITableView
3.Rx ๋ฉ์๋ items(cellIdentifier:cellType:)์ ํธ์ถํฉ๋๋ค. cell Identifier์ cell type ์ class๋ฅผ ํ๋ผ๋ฏธํฐ๋ก ๋๊ฒจ์ฃผ๋ฉด์ ํธ์ถํฉ๋๋ค. ์ด๊ฒ์ Rx framework๊ฐ ๋ณดํต tableView๊ฐ ๊ทธ๊ฒ์ ์๋ delegate๋ค์ ๊ฐ์ง๊ณ ์๋ค๋ฉด ํธ์ถ๋ dequeuing ๋ฉ์๋๋ฅผ ํธ์ถํ ์ ์๋๋ก ํฉ๋๋ค.
4.๊ฐ๊ฐ์ ์๋ก์ด item ์ ๋ํด์ ์คํ๋ Block์ ๋๊น๋๋ค. row, row์ ์๋ ์ด์ฝ๋ฆฟ ๊ทธ๋ฆฌ๊ณ cell ์ ๋ํ ์ ๋ณด๋ฅผ ๋ค์ ์ป๊ฒ ๋ ๊ฒ๋๋ค. ์ด๊ฒ์ cell ์ค์ ์ ์ ๋ง ์ฝ๊ฒ ๋ง๋ญ๋๋ค.
5.bindTo(_:)์ ์ํด์ ๋๋ ค ๋ฐ์๋ Disposable ์ disposeBag ์ ์ถ๊ฐ ํฉ๋๋ค.
๋ณดํต tableView(:numberOfRowsInSection) ๊ณผ numberOfRowsInSection(in:) ์ ์ํด์ ์์ฑ๋๋ ๊ฐ๋ค์ ์ด์ ๊ด์ฐฐ๋๋(observed) data์ ๊ธฐ๋ฐํด์ ์๋์ผ๋ก ๊ณ์ฐ๋ฉ๋๋ค. tableView(:cellForRowAt:)์ ํด๋ก์ ์ ์ํด์ ํจ๊ณผ์ ์ผ๋ก ๋์ฒด๋ฉ๋๋ค.
viewDidLoad()๋ก ๊ฐ์ธ์. ๊ทธ๋ฆฌ๊ณ ์๋ก์ด setup ๋ฉ์๋๋ฅผ ํธ์ถํ๋ ์ค์ ์ถ๊ฐ ํ์ธ์.
setupCellConfiguration()
์ฑ์ ๋น๋ํ๊ณ ์คํ์ํค์ธ์. ๊ทธ๋ ์ต๋๋ค! ์ด์ฝ๋ฆฟ๋ค์ด ๋์ ์์ต๋๋ค.
ํ์ง๋ง, ๊ฐ๊ฐ์ ์ด์ฝ๋ฆฟ์ ํญ์ ํ ๋, ์นด๋์ ๋ํด์ง์ง ์์ต๋๋ค. ์ด์ ์ Rx Method ๋ค์์ ๋ญ๊ฐ ์๋ชป๋์๋์?
์๋๋๋ค! tableView(_:didSelectRowAt:)๋ฅผ ์ ๊ฑฐํจ์ผ๋ก์จ, cell์ ๋ํ ํญ๋ค์ ๊ฐ์ ธ์ค๊ฑฐ๋ ๊ทธ๊ฒ๋ค์ ์ด๋ป๊ฒ ์ฒ๋ฆฌํด์ผ ํ๋์ง ์์ ์๋ ์ด๋ค ๊ฒ์ ๊ฐ์ ธ์์ต๋๋ค.
์ด๊ฒ์ ํด๊ฒฐํ๊ธฐ ์ํด์, RxCocoa๊ฐ UITableView์ ์ถ๊ฐํ ๋ ๋ค๋ฅธ extension method๊ฐ ์์ต๋๋ค. ์ด ๋ฉ์๋๋ modelSelected(_:)๋ผ๊ณ ๋ถ๋ฆฝ๋๋ค. ๊ทธ๋ฆฌ๊ณ model ๊ฐ์ฒด๋ค์ด ์ธ์ ์ ํ๋ ๋, ์ ๋ณด๋ฅผ ๋ณด๋๋ฐ ์ฌ์ฉํ ์ ์๋ Observable์ ๋ฆฌํดํฉ๋๋ค.
๋ค์ ๋ฉ์๋๋ฅผ ์ถ๊ฐํ์ธ์:
private func setupCellTapHandling() {
tableView
.rx
.modelSelected(Chocolate.self) //1
.subscribe(onNext: { //2
chocolate in
ShoppingCart.sharedCart.chocolates.value.append(chocolate) //3
if let selectedRowIndexPath = self.tableView.indexPathForSelectedRow {
self.tableView.deselectRow(at: selectedRowIndexPath, animated: true)
} //4
})
.addDisposableTo(disposeBag) //5
}
ํ ๋จ๊ณ์ฉ ์ด๋ ๊ฒ ๋ฉ๋๋ค.:
1.talbleView์ reactive extension์ modelSelected(_:) ํจ์๋ฅผ ํธ์ถํฉ๋๋ค. ์ ์ ํ ํ์ ์ item์ ๋๋ ค ๋ฐ๊ธฐ ์ํด์ Chocolate model์ ๊ฐ์ด ๋๊น๋๋ค. ์ด ํจ์๋ Observable์ ๋ฆฌํดํฉ๋๋ค.
2.Observable์ ๋ฐ์ผ๋ฉด, subscribe(onNext:)๋ฅผ ํธ์ถํฉ๋๋ค. ๋ชจ๋ธ์ด ์ ํ๋ ๋๋ง๋ค(์๋ฅผ ๋ค์ด cell์ด ํญ๋ ๋) ์ค์๋์ด์ผ ํ๋ ํด๋ก์ ๋ฅผ ๋ค๋ฐ๋ฅด๊ฒ ํด์ ํธ์ถํฉ๋๋ค.
3.subscribe(onNext:)์ ๋๊ฒจ์ง ๋ค๋ฐ๋ฅด๋ ํด๋ก์ ์์์, ์ ํ๋ ์ด์ฝ๋ฆฟ์ ์นดํธ์ ๋ฃ์ต๋๋ค.
4.์ญ์ ํด๋ก์ ์์์ ํญ๋ row๊ฐ deselect๊ฐ ๋๋๋ก ํฉ๋๋ค.
5.subscribe(onNext:)๋ Disposable ๋ฅผ ๋ฆฌํดํฉ๋๋ค. disposeBag์ ๊ทธ Disposable์ ์ถ๊ฐํฉ๋๋ค.
๋ง์ง๋ง์ผ๋ก, viewDidLoad()๋ก ๊ฐ๋๋ค. ๊ทธ๋ฆฌ๊ณ ์๋ก์ด ์ค์ ๋ฉ์๋๋ฅผ ์ถ๊ฐํฉ๋๋ค.
setupCellTapHandling()
์ฑ์ ๋น๋ํ๊ณ ์คํํ์ธ์. ์น์ํ ์ด์ฝ๋ฆฟ ๋ฆฌ์คํธ๋ฅผ ๋ณผ ์ ์์ ๊ฒ๋๋ค.
ํ์ง๋ง ์ด์ ๋ ์ด์ฝ๋ฆฟ์ ์ถ๊ฐํ ์ ์์ต๋๋ค!
RxSwift์ ๋ค๋ฅธ ์ ์ฉํ ๊ธฐ๋ฅ์ ์ ์ ์ ์ํ ์ง์ text ์ ๋ ฅ์ ๋ฐ๊ณ ๋ฐ์ํ ์ ์๋ ๊ธฐ๋ฅ์ ๋๋ค.
๋ฐ์ํ text ์ ๋ ฅ ์ฒ๋ฆฌ์ ๋ง๋ณด๊ธฐ๋ฅผ ์ํด, ์ ์ฉ card ์ ๋ ฅ ํผ์ ๊ฐ๋จํ validation ๊ณผ card type ๊ฐ์ง ๊ธฐ๋ฅ์ ์ถ๊ฐํ ๊ฒ์ ๋๋ค.
Non-reactive ํ๋ก๊ทธ๋จ์์๋ ์ ์ฉ card ์ ๋ ฅ ํผ์ด UITableViewDelegate ๋ฉ์๋๋ค์ด ๋ค์์ผ์ ์ฒ๋ฆฌ๋ฉ๋๋ค. ์ข ์ข ๊ฐ๊ฐ์ ํจ์๋ค์ ๋ง์ ์ด๋ค textField๊ฐ ์ ๋ ฅ๋๊ณ ์๋์ง์ ๊ธฐ๋ฐํ์ฌ ์ด๋ค ๋์๊ณผ ๋ก์ง์ด ์ ์ฉ๋์ด์ผ ํ๋์ง๋ฅผ ๊ตฌ๋ถํ๋ if/else ๋ฌธ์ ๊ฐ์ง๊ณ ์์ต๋๋ค.
Reactive Programming ์ ๊ทธ ์ฒ๋ฆฌ๋ค์ ์ข ๋ ์ง์ ์ ์ผ๋ก ๊ฐ๊ฐ์ ์ ๋ ฅ field์ ๋ฌถ์ต๋๋ค. ๋ง์ฐฌ๊ฐ์ง๋ก ์ด๋ค ๋ก์ง๋ค์ด ์ด๋ค text field์ ์ ์ฉ๋๋์ง๋ฅผ ๋ช ํํ ํ๋ฉด์ ์ ๋ ฅ field์ ๋ฌถ์ต๋๋ค.
BillingInfoViewController.swift๋ก ๊ฐ์ธ์. ํด๋์ค์ ์ฒ์์ ๋ค์์ ์ถ๊ฐํ์ธ์.
private let disposeBag = DisposeBag()
์ด์ ์ฒ๋ผ, ์ด๊ฒ์ ํด๋์ค ์ธ์คํด์ค๊ฐ deallocte ๋ ๋ Observable ๋ค์ด ์ ์ ํ๊ฒ ํ๊ธฐ๋๋๋ก ํ๊ธฐ ์ํด์ DisposeBag์ ์ ์ํฉ๋๋ค.
์ ์ ๊ฐ ์ ์ฉ card ๋ฒํธ๋ฅผ ์ ๋ ฅํ ๋ ๋์์ด ๋๋ ํ๊ฐ์ง๋ known card types( https://en.wikipedia.org/wiki/Payment_card_number )์ ๊ธฐ๋ฐํ์ฌ ์ ์ ๊ฐ ์ ๋ ฅํ๊ณ ์๋ ์ ์ฉ card์ ์ข ๋ฅ๊ฐ ๋ฌด์์ธ์ง ๋ณด์ฌ์ฃผ๋ ๊ฒ์ ๋๋ค.
์ด๊ฒ์ ํ๊ธฐ ์ํด์, //MARK: - Rx Setup ์ฃผ์ ์๋์ ๋ค์์ ์ถ๊ฐํ์ธ์.
//MARK: - Rx Setup
private func setupCardImageDisplay() {
cardType
.asObservable()
.subscribe(onNext: {
cardType in
self.creditCardImageView.image = cardType.image
})
.addDisposableTo(disposeBag)
}
๊ณง, card type ๋ณํ์ ๊ธฐ๋ฐํด์ ์นด๋ ์ด๋ฏธ์ง๋ฅผ ์ ๋ฐ์ดํธ ํ ๊ฒ๋๋ค. Observer๋ฅผ Variable์ value์ ์ถ๊ฐํฉ๋๋ค. Variable์ value๊ฐ ๋ณ๊ฒฝ๋ ๋ ์คํ๋ ํด๋ก์ ์ ํจ๊ป ์ถ๊ฐํฉ๋๋ค. ๊ทธ๋ฆฌ๊ณ ๋์ observer๊ฐ disposeBag์์ ์ ์ ํ๊ฒ ํ๊ธฐ๋๋๋ก ํฉ๋๋ค.
์ด์ ์ฌ๋ฏธ์๋ ๋ถ๋ถ์ ๋๋ค.:Text ๋ณ๊ฒฝ ์ฒ๋ฆฌ
์ ์ ๊ฐ ๋น ๋ฅด๊ฒ ํ์ดํํ ์ ์๊ธฐ ๋๋ฌธ์, ๋ชจ๋ ๊ธ์ ํค๊ฐ ๋๋ฌ์ง ๋๋ง๋ค validation์ ์คํ์ํค๊ณ ์ถ์ง ์์ ๊ฒ๋๋ค. ๋งค๋ฒ validation ๊ฒ์ฌ๋ฅผ ํ๋ ๊ฒ์ ๋น์ฉ์ ์ผ๋ก ๋น์ธ๊ณ , ๋๋ฆฐ UI๋ฅผ ์ ๋ฐํฉ๋๋ค.
์ด๊ฒ์ ์ฒ๋ฆฌํ๋ ์ข์ ๋ฐฉ๋ฒ์ validation process์ ์ผ๋ง๋ ๋นจ๋ฆฌ ์ฌ์ฉ์์ ๋ ฅ์ ๋ฃ์ ๊ฒ์ธ์ง debounce ํน์ throttle ํ๋ ๊ฒ์ ๋๋ค. ์ด๊ฒ์ ์ ๋ ฅ๋๋ ๋ชจ๋ ๊ธ์๊ฐ ๋ณ๊ฒฝ๋ ๋๋ง๋ค ์ฒ๋ฆฌ๋๊ธฐ ๋ณด๋ค๋ throttle ๊ฐ๊ฒฉ์ ์ฌ์ฉ์ ์ ๋ ฅ validation ๊ฒ์ฌ๊ฐ ์ด๋ฃจ์ด ์ง๋ค๋ ๊ฒ์ด๋ผ๋ ๊ฒ์ ์๋ฏธํฉ๋๋ค. ์๋ฌด๋ฆฌ ๋น ๋ฅธ ์ ๋ ฅ๋ ์ฑ์ ๋ฉ์ถ๊ฒ ํ์ง ์์ต๋๋ค.
๋ณ๊ฒฝ๋ ๋๋ง๋ค ์คํ๋์ด์ผ ํ ๋ก์ง์ด ์ข ์ข ๋ง๊ธฐ ๋๋ฌธ์, throttle ํ๋ ๊ฒ์ RxSwift์ ํน๋ณํ ์ฐ์ํ ์ ์ ๋๋ค. ์ด ๊ฒฝ์ฐ์๋ ํฌ๊ฒ ๋ก์ง์ด ๋ง์ง ์์ง๋ง, ์์ throttle๋ฅผ ๋ง๋ค ์ถฉ๋ถํ ๊ฐ์น๊ฐ ์์ต๋๋ค.
์ฐ์ , BillingInfoViewController์ property ์ ์ธ ์๋ ๋ค์์ ์ฝ๋๋ฅผ ์ถ๊ฐ ํ์ธ์.
private let throttleInterval = 0.1
์ด๊ฒ์ ์ด๋จ์ throttle ๊ธธ์ด์ ๋ํ ์์๊ฐ์ ๋๋ค.
์ด์ ๋ค์์ ์ถ๊ฐ ํ์ธ์.
private func setupTextChangeHandling() {
let creditCardValid = creditCardNumberTextField
.rx
.text //1
.throttle(throttleInterval, scheduler: MainScheduler.instance) //2
.map { self.validate(cardText: $0) } //3
creditCardValid
.subscribe(onNext: { self.creditCardNumberTextField.valid = $0 }) //4
.addDisposableTo(disposeBag) //5
}
์ฃผ์: ๋ง์ฝ creditCardValid๋ฅผ ์ค์ ํ ๋ "Generic parameter R could not be inferred" ์ปดํ์ผ ์๋ฌ๊ฐ ๋ฐ์ํ๋ค๋ฉด, ํ์ ์ ๋ช ์์ ์ผ๋ก ์ ์ธํจ์ผ๋ก์จ Compiler๋ฅผ ์กฐ์ฉํ๊ฒ ๋ง๋ค์ ์์ต๋๋ค. (์๋ฅผ ๋ค์ด let creditCardValid: Observable). ์ด๋ก ์ ์ผ๋ก, ์ปดํ์ผ๋ฌ๋ ์ด๊ฒ์ ์ถ๋ก ํ ์ ์์ด์ผ ํฉ๋๋ค. ํ์ง๋ง ๊ฐ๋ ์กฐ๊ธ ๋์์ด ํ์ํฉ๋๋ค.:]
์ด ์ฝ๋๊ฐ ํ๋ ๊ฒ์ ๋๋ค.:
1.text ๋ ๋ ๋ฐ๋ฅธ RxCocoa extension์ ๋๋ค.(์ฌ์ฉํ๊ธฐ ์ ์ rx๋ฅผ ํธ์ถํจ์ผ๋ก์จ ์นด๋ฆฌ์ผ์ง๋ RxCocoa extension์ ๋๋ค.) ์ด๋ฒ์๋ UITextField์ ๋ํ ๊ฒ์ ๋๋ค. ๊ทธ๊ฒ์ Observable value๋ก์จ textField์ ๋ด์ฉ์ ๋ฆฌํดํฉ๋๋ค.
2.์ ๋ ฅ์ throttleํฉ๋๋ค. ๊ทธ๋์ ์์์ ์ ์ํ ๊ฐ๊ฒฉ์ ๊ธฐ๋ฐํ ๋๋ก๋ง ์ค์ ํ validation์ด ์คํ๋ฉ๋๋ค. sheduler ํ๋ผ๋ฏธํฐ๋ ๋ ์ง๋ณด๋ ๊ฐ๋ ์ ๋๋ค. ํ์ง๋ง ์งง์ ๋ฒ์ ์ ๊ทธ๊ฒ์ ์ค๋ ๋๋ก ํ์ ๋ฉ๋๋ค. ๋ฉ์ธ ์ค๋ ๋์์ ๋ชจ๋ ์ ์งํ๊ธฐ๋ฅผ ์ํ๊ธฐ ๋๋ฌธ์, MainScheduler๋ฅผ ์ฌ์ฉํฉ๋๋ค.
3.throttle๋ ์ ๋ ฅ์ validate(cardText:)์ ์ ์ฉํจ์ผ๋ก์จ throttle๋ ์ ๋ ฅ์ ๋ณํ์ํต๋๋ค. validate(cardText:)๋ ์ด๋ฏธ ํด๋์ค์ ์ํด์ ์ ๊ณต๋๊ณ ์์ต๋๋ค. ๋ง์ฝ ์นด๋ ์ ๋ ฅ์ด ์ ํจํ๋ค๋ฉด, ๊ด์ฐฐ๋๋(observed) boolean ์ ์ต์ข ๊ฐ์ true๊ฐ ๋ ๊ฒ์ ๋๋ค.
4.์ ๋ ฅ๋๋ ๊ฐ์ ๊ธฐ๋ฐํ textField ์ ์ ํจ์ฑ์ด ์ ๋ฐ์ดํธ๋๋ Observable ์ subscribe ํฉ๋๋ค.
5.๊ฒฐ๊ณผ์ธ Disposable ์ disposeBag ์ ์ถ๊ฐํฉ๋๋ค.
(CVV๋ก ์๋ ค์ง)card security code ์ expiration date ์ ๋ํ Observable ๊ฐ๋ค์ ์์ฑํ๊ธฐ ์ํด์
๋ค์์ ์ฝ๋๋ฅผ setupTextChangeHandling() ์ ๋์ ์ถ๊ฐ ํ์ธ์.
let expirationValid = expirationDateTextField
.rx
.text
.throttle(throttleInterval, scheduler: MainScheduler.instance)
.map { self.validate(expirationDateText: $0) }
expirationValid
.subscribe(onNext: { self.expirationDateTextField.valid = $0 })
.addDisposableTo(disposeBag)
let cvvValid = cvvTextField
.rx
.text
.map { self.validate(cvvText: $0) }
cvvValid
.subscribe(onNext: { self.cvvTextField.valid = $0 })
.addDisposableTo(disposeBag)
์ด์ 3๊ฐ์ text field๋ค์ ์ ํจ์ฑ์ ๋ํด ์ค์ ๋ Observable ๊ฐ๋ค์ ์ป์์ต๋๋ค. ๋ค์์ ์ถ๊ฐํ์ธ์.
let everythingValid = Observable
.combineLatest(creditCardValid, expirationValid, cvvValid) {
$0 && $1 && $2 //All must be true
}
everythingValid
.bindTo(purchaseButton.rx.enabled)
.addDisposableTo(disposeBag)
์ด ์ฝ๋๋ Observable์ combineLatest(_:) ๋ฉ์๋๋ฅผ ์ฌ์ฉํฉ๋๋ค. ์ด๋ฏธ ๋ง๋ค์๋ 3๊ฐ์ Observable๋ค์ ๋ฐ๊ธฐ ์ํด์ ์ฌ์ฉํฉ๋๋ค. ๊ทธ๋ฆฌ๊ณ 4๋ฒ์งธ Observable์ ๋ง๋ญ๋๋ค. ์ด Observable์ verythingValid ์ด๋ผ๋ ๋ถ๋ฆฝ๋๋ค. ๊ทธ๋ฆฌ๊ณ ์ด Observable์ ๋ชจ๋ 3๊ฐ์ ์ ๋ ฅ์ด ์ ํจํ์ง ์๋์ง์ ๋ฐ๋ผ true ํน์ false ๊ฐ์ ๋๋ค.
๊ทธ๋ฐ ๋ค์ everythingValid ๋ UIButton์ reactive extnsion์ enabled property์ bind ๋ฉ๋๋ค. ๊ทธ๋์ ๊ตฌ๋งค ๋ฒํผ์ ์ํ๊ฐ everythingValid์ ๊ฐ์ ์ํด์ ์ ์ด๋ฉ๋๋ค.
๋ง์ฝ ๋ชจ๋ ์ธ ๊ฐ๋ค์ด ์ ํจํ๋ค๋ฉด, ๊ธฐ๋ฐํ๋ everythingValid์ ๊ฐ์ด true์ผ ๊ฒ๋๋ค. ๋ง์ฝ ๊ทธ๋ ์ง ์๋ค๋ฉด, ๊ธฐ๋ฐํ ๊ฐ์ด false ์ผ ๊ฒ๋๋ค. ๋ค๋ฅธ ๊ฒฝ์ฐ์ rx.enabled๋ ๊ตฌ๋งค ๋ฒํผ์ ์ ์ฉ๋ ๊ธฐ๋ฐ ๊ฐ์ด ์ ์ฉ๋๋๋ก ํ ๊ฒ์ ๋๋ค. ๊ทธ๋์ ์นด๋ ์์ธ ์ ๋ณด๊ฐ ์ ํจํ ๋๋ง enable ๋ฉ๋๋ค.
์ด์ setup methods๋ฅผ ๋ง๋ค์์ต๋๋ค. viewDidLoad()์ ๊ทธ๊ฒ๋ค์ ์ถ๊ฐ ํ์ธ์.
setupCardImageDisplay()
setupTextChangeHandling()
์ฑ์ ๋น๋ํ๊ณ ์คํ์ํค์ธ์. ์ ์ฉ์นด๋ ์ ๋ ฅ์ ๋ฐ๊ธฐ ์ํด์ ์ด์ฝ๋ฆฟ์ ํญํ์ฌ ์นดํธ์ ์ถ๊ฐํ๊ณ , ์นดํธ๋ก ๊ฐ๊ธฐ์ํด์ ์นดํธ ๋ฒํผ์ ํญํ์ธ์. ์ต์ ํ๋ ์ด์์ ์ด์ฝ๋ฆฟ์ด ์นดํธ์ ์๊ธฐ ๋๋ฌธ์, ์ฒดํฌ์์ ๋ฒํผ์ด ํ์ฑํ ๋์ด์ผ ํฉ๋๋ค.
์นด๋ ์ ๋ ฅ ํ๋ฉด์ ๋ณด์ฌ์ค ์ฒดํฌ ์์ ๋ฒํผ์ ํญํ์ธ์.
Card Number text field์ 4๋ฅผ ํ์ดํํ์ธ์. - ์นด๋ ์ด๋ฏธ์ง๊ฐ ์ฆ์ Visa๋ก ๋ณํ๋ ๊ฒ์ ๋ณผ ์ ์์ ๊ฒ๋๋ค.
4๋ฅผ ์ง์ฐ์ธ์. ๊ทธ๋ฆฌ๊ณ ์นด๋ ์ด๋ฏธ์ง๊ฐ unknown์ผ๋ก ๋ณ๊ฒฝ๋ ๊ฒ๋๋ค. 55๋ฅผ ์ ๋ ฅํ์ธ์. ์ด๋ฏธ์ง๊ฐ MasterCard๋ก ๋ณ๊ฒฝ๋ ๊ฒ๋๋ค.
๋ฉ์ง๋๋ค! ์ด ์ฑ์ ๋ฏธ๊ตญ์์ 4๊ฐ์ ๋ฉ์ด์ ํ์ ์ ๋ํด์ ์ง์ํฉ๋๋ค.(Visa, MasterCard, Amerian Express, Discover). ๋ง์ฝ ์ด ์นด๋ ํ์ ๋ค์ค์์ ํ๋๋ฅผ ๊ฐ์ง๊ณ ์๋ค๋ฉด, ์ด๋ฏธ์ง๊ฐ ์ ๋๋ก ๋์ค๊ณ ๋ฒํธ๊ฐ ์ ํจํ์ง๋ฅผ ๋ณด๊ธฐ ์ํด์ ์นด๋ ๋ฒํธ๋ฅผ ์ ๋ ฅํด๋ณผ ์ ์์ต๋๋ค.
์ฃผ์:๋ง์ฝ ์ด๋ค ์ ์ฉ ์นด๋ ์ค ํ๋๋ฅผ ๊ฐ์ง๊ณ ์์ง ์๋ค๋ฉด, PayPal()์ด ๊ทธ๋ค์ card sandbox๋ฅผ ํ ์คํธํ๊ธฐ ์ํด์ ์ฌ์ฉํ๋ ์นด๋ ๋ฒํธ ์ค ํ๋๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค. ์ด ์นด๋ ๋ฒํธ๋ค์ ๋น๋ก ์ค์ ๋ก ์ฌ์ฉํ ์๋ ์์ง๋ง, ์ฑ์์ ๋ชจ๋ validation์ ํต๊ณผ ํด์ผ ํฉ๋๋ค.
์ ํจํ ์นด๋ ๋ฒํธ๊ฐ ๋ฏธ๋์ ์ด๋ ์๊ฐ์ (2์๋ฆฌ ์๊ณผ 4์๋ฆฌ ๋ ๋๋ฅผ ์ฌ์ฉํ๋)expiration date, ์ ์ ํ ๊ธธ์ด์ CVV์ ํจ๊ป ์ ๋ ฅ๋๋ฉด, Buy Chocolate! ๋ฒํผ์ด ํ์ฑํ๋ ๊ฒ๋๋ค.
์ฌ๋ฌ๋ถ์ด ์ฐ ๊ฒ๊ณผ ์ผ๋ง๋ ์ง๋ถํ๋์ง์ ๋ํ ์์ฝ ๊ทธ๋ฆฌ๊ณ ์์ ์ด์คํฐ ์๊ทธ๋ฅผ ๋ณด๋ ค๋ฉด, ๊ตฌ๋งค ๋ฒํผ์ ํญํ์ธ์.
์ถํํฉ๋๋ค! RxSwift์ RxCocoa์๊ฒ ๊ฐ์ฌํฉ๋๋ค. ์ฌ๋ฌ๋ถ์ ์น๊ณผ์์ฌ๊ฐ ๋ญ๋ผ๊ณ ํ์ง ์์ ๋งํผ ์ด์ฝ๋ฆฟ์ ์ด์ ์์ต๋๋ค.
์๋ฃ๋ ํ๋ก์ ํธ๋ ์ฌ๊ธฐ( https://koenig-media.raywenderlich.com/uploads/2016/10/Chocotastic-finished-s3-rxs-3b1.zip )์์ ์ฐพ์ ์ ์์ต๋๋ค.
๋ง์ฝ ๋์ ์ ์ํ๋ค๋ฉด, ์ด ์ฑ์ ์ข ๋ reactiveํ๊ฒ ์ํด์ ๋ช๊ฐ์ง ์ถ๊ฐํด ๋ณด์ธ์.
*CartViewController๋ฅผ (label ๋์ ์)reactive table view๋ฅผ ์ด์ฉํด์ ์นดํธ์ ๋ด์ฉ์ ํ์ํ๋๋ก ์์ ํด ๋ณด์ธ์.
*์ ์ ๊ฐ ์นดํธ์์ ๋ฐ๋ก ์ด์ฝ๋ฆฟ์ ์ถ๊ฐํ๊ฑฐ๋ ์ญ์ ํ ์ ์๋๋ก ํด๋ณด์ธ์. ๊ฐ๊ฒฉ๋ ์๋์ผ๋ก ์ ๋ฐ์ดํธ ๋๋๋ก ํ๊ตฌ์.
์ด์ Rx programming์ ๋ง์ ๋ณด์์ต๋๋ค. ์ฌ๊ธฐ ๋น์ ์๊ฒ ๋น์ฐ์ ์ฌํ์ ๊ณ์ํ๋๋ก ๋์ธ ๋ฆฌ์์ค๊ฐ ๋ ์์ต๋๋ค.
*RxSwift Slack( http://slack.rxswift.org/ )
*RxSwiftโs Getting Started guide( https://github.com/ReactiveX/RxSwift/blob/master/Documentation/GettingStarted.md )
*Max Alexanderโs talk on Rx at Realm ( https://realm.io/news/slug-max-alexander-functional-reactive-rxswift/ )
๋ง์ง๋ง์ผ๋ก, Marin Todorov( https://realm.io/news/slug-max-alexander-functional-reactive-rxswift/ )๋ rx_marin( http://rx-marin.com/ )์ด๋ผ๊ณ ๋ถ๋ฆฌ๋ Reactive programming์ ์์ด์ ๊ทธ์ ๋ชจํ์ ๋ํ ํ๋ฅญํ ๋ธ๋ก๊ทธ๋ฅผ ๊ฐ์ง๊ณ ์์ต๋๋ค. ํ์ธํด ๋ณด์ธ์!
๋ง์ฝ ์ด ํํฐ๋ฆฌ์ผ์ ํตํด์ ๋ฐฐ์ฐ๋ ๊ฒ์ด ์ฆ๊ฑฐ์ฐ์ จ๋ค๋ฉด, ์คํ ์ด์ ์๋ RxSwift book( https://store.raywenderlich.com/products/rxswift?_ga=1.9745141.1558722521.1451401789 )์ ํ์ธํด๋ณด๋ ๊ฒ์ ์ด๋ ์ธ์?
์ด๊ฒ์ ์ด ์ฑ ์ ์๋ ๋ง๋ณด๊ธฐ ์ ๋๋ค.:
-
์์ํ๊ธฐ: Reactive programming ํจ๋ฌ๋ค์์ ๋ํ ์๊ฐ๋ฅผ ์ป๊ณ , ํฌํจ๋ ์ฉ์ด๋ฅผ ๋ฐฐ์ฐ์ธ์. ๊ทธ๋ฆฌ๊ณ ์ฌ๋ฌ๋ถ์ ํ๋ก์ ํธ์์ ์ด๋ป๊ฒ RxSwift๋ฅผ ์์ํ ์ง ์์ ๋ณด์ธ์.
-
Event Management: Rx- Observable๋ค๊ณผ Observer๋ค์ ๋๊ฐ์ง ๊ฐ๋ ์ ๊ฐ์ง๊ณ ๋น๋๊ธฐ ์ด๋ฒคํธ sequence๋ค์ ์ฒ๋ฆฌํ๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์ฐ์ธ์.
-
Selective ๋๊ธฐ: filter, transform, combine, ๊ณผ time operator๋ค๊ณผ ๊ฐ์ ๊ฐ๋ ์ ์ด์ฉํด์ ๋ค์ํ ์ด๋ฒคํธ๋ค์ ์ฒ๋ฆฌํ๋ ๋ฐฉ๋ฒ์ ์์ ๋ณด์ธ์.
-
UI Development: RxSwift๋ RxCocoa๋ฅผ ์ฌ์ฉํ๋ ์ฑ์ UI์์ ์ ์ฝ๊ฒ ํฉ๋๋ค. RxCocoa๋ Cocoa์ UIKit ๋์ ํตํฉ์ ์ ๊ณตํฉ๋๋ค.
-
intermediate Topics: reactive networking, multi-threading, error handling์ ๊ดํ ์ฌ๋ฌ๋ถ์ RxSwift ์ง์์ ๋ ๋ฒจ์ ํ์ธ์.
-
Advanced Topcis: MVVM ์ฑ ๊ตฌ์กฐ, scene-based navigation, service๋ค์ ํตํ ๋ฐ์ดํฐ ๋ ธ์ถ์ ๋ํด ๋ฐฐ์์ผ๋ก์จ RxSwift ๊ต์ก์ ๋ง๋ฌด๋ฆฌ ์ง์ฐ์ธ์.
-
๊ทธ๋ฆฌ๊ณ ๋ ๋ ๋!
์ด ์ฑ ์ ๋ ์ฏค์ reactive ํจ๋ฌ๋ค์์์ ์ผ๋ฐ์ ์ธ ์ด์๋ฅผ ํด๊ฒฐํ๋ ์ค์ ๊ฒธํ์ ์ป์ ์ ์์ต๋๋ค. - ๊ทธ๋ฆฌ๊ณ ์ฌ๋ฌ๋ฒ ์์ ์ Rx ํจํด๊ณผ ์๋ฃจ์ ๋ค์ ์๊ฐํด ๋ด๋๋ฐ ์ ๋๋ก ์ ๊ทผํ๊ฒ ๋ ๊ฒ์ ๋๋ค.
์ง๋ฌธ์ด ์์ผ์๊ฑฐ๋ ์ ๊ณตํ ๋ค๋ฅธ Rx ๋ฆฌ์์ค๊ฐ ์์ผ์๋ค๋ฉด? ์๋์ ์ฝ๋ฉํธ๋ฅผ ๋จ๊ฒจ ์ฃผ์๊ฑฐ๋ ํฌ๋ผ์ ๋จ๊ฒจ ์ฃผ์ธ์.
๊ฐ์ฌํฉ๋๋ค.










