[Clone/Instagram] ๐ฉ LoginController MVVM ๋ฆฌํํฐ๋ง(์๋ฌ์ฒ๋ฆฌ, clean code) with combine | LoginController Code Review ๐๏ธ | ๋ง์ฃผํ ์๋ฌ & ๊ฐ์ ํด์ผ ํ ๊ธฐ๋ฅ & ๋๋์ & #13
SHcommit opened this issue ยท comments
TODO: ์ด๋ฒ์ Never์ธ ๊ฒฝ์ฐ๋ ์๋ค๋ ์๊ฐ์ผ๋ก LoginController ๋ฆฌํํฐ๋ง.
๊ตฌํ ์์
๊ฐ๋จํ LoginController ์ฝ๋ ๋ฆฌ๋ทฐ ( ์ํ ๋๋๋ ๊น๋จน์ง ์๊ธฐ ์ํด.. )
๐ TODO: ์ฌ์ฉ์๊ฐ ๋ก๊ทธ์ธ ํ๋ ๊ณผ์ ์ ์ปด๋ฐ์ธ์ผ๋ก ๊ตฌํ with mvvm pattern + DI(Dependency Injection). ์ด๋ ๋ฐ์ํ ์ ์๋ ๋ชจ๋ ์ค๋ฅ throws -> ์ฒ๋ฆฌ.
-
LoginController
-
LoginViewModelProtocols
-
LoginViewModel
์ฌ์ฉ์์ ๋ก๊ทธ์ธ ๊ธฐ๋ฅ, ํ๋ฉด๊ณผ ๊ด๋ จ์๋(ํ์๊ฐ์ ๋นผ๊ณ !!) ์์ค์ฝ๋๋ ์์ ์๋ ๋จ 3๊ฐ๋ก ๊ตฌ์ฑํ๋ค.
1. LoginController์ ๊ดํด (LoginController Link)
- input/output pattern. DI
๊ตฌํ์์ ์ด๊ธฐ ํ๋ฉด์์ ๋ณด๋ฏ LoginController์ view๋ 2๊ฐ์ textfield(id, pw ์ ๋ ฅ) login button์ผ๋ก ๊ตฌ์ฑ๋๋๋ค. RxSwift์์ MVVM ํจํด์ ์ฌ์ฉํ ๋ input/ output pattern์ ์ด์ฉํ๋ค. ์ด๋ฅผ ์ด์ฉํ๋ฉด Dependency Injection(DI) ๋ํ ๊ฐ๋ฅํ๋ค. LoginController๊ฐ ์ด๊ธฐํ ๋ ๋ ๋ฐ๋์ LoginViewModelType๋ก ์ด๊ธฐํ ํจ์ผ๋ก DIํ๋ค.
- input/output implement with error handling
LoginController๋ viewModel์ ์ฌ์ฉํ ๋ ๋ง์ ํจ์๊ฐ ํ์ ์๋ค. ๊ทธ์ input, ouput์ ๋ฐ์ธ๋ฉ ํ๊ธฐ๋ง ํ๋ฉด ๋๋ค. ๋ฐ๋ผ์ LoginController์์๋ viewModel ๋ณ์๋ฅผ ํตํด transform ๋จ ํ๊ฐ์ ํจ์๋ง ์คํํ๋๋ก ํ๋ค. ์? input/output pattern์ ํตํด์ LoginController์์ ๋ฐ์๋๋ ๋ชจ๋ view์ Event flow handling์ viewModel์์ ํ๊ธฐ ๋๋ฌธ์ด๋ค.
- render(in:)
ํ๊ฐ์ง ๋! LoginController์์ ์ฃผ์ํ ํจ์๋ renderํจ์์ด๋ค. LoginController์์ ๋ฐ์ํ ๊ฐ๊ฐ์ ์ด๋ฒคํธ์ ๋ํด์ viewModel์ transform(input:) ํจ์๋ฅผ ํตํด ํน์ publisher์ chains ํจ์๋ฅผ ํตํ ํ๋ฆ ์ฒ๋ฆฌ ์ดํ์ ๊ฐ์ output๋ก ๋ฐ๋๋ค.
์ด๋์ output ์ข ๋ฅ๋ LoginControllerState์ ๋ฐ๋ฅธ 3๊ฐ์ง๊ฐ ์๋ค. ์ฃผ ์ญํ ์ view์ ์ํ ๋ณํ๋ฅผ ์ํ output์ enum์ผ๋ก ์ ์ํ ๊ฒ ๋ฟ์ด๋ค. ๊ทธ์ state์ ๋ฐ๋ผ view์ ์ํ ๋ณํ๋ฅผ ์คํํ๋ฉด ๋๋ค.
2. LoginViewModelProtocols์ ๊ดํด (LoginViewModelProtocols Link)
LoginViewModel์ ์ค๋ช ํ๊ธฐ ์ ์ ์์ protocols๋ฅผ ๋ณด๋ฉด์ ์์ ํ๋ฉด ํธํ๋ค. ๋งจ ์ฒ์์ delegate๊ฐ ์๋๋ฐ ์ถํ ์์จ ๊ณํ์ด๋ค. (๋ธ๋ฆฌ๊ฒ์ดํธ ๋๋งค ๊ผฌ์ด๋ ๋๋์ด..)
- viewModel's all error type
LoginController์ upstream input๋ก ์ค๋ ๊ฐ๊ฐ์ publisher์ ๋ํด stream operator chains๋ฅผ ์ํํ ๋ ๋ฐ์ ํ ์ ์๋ ๋ชจ๋ ์ค๋ฅ๋ฅผ ์ ์ํ๋ค. ๋งจ ์ ์ ๋ชฉ์์ ๋ณด๋ฏ Neverํ์ ์ด๋ ์๋ค๊ณ ๊ฐ์ ํ๊ณ ๋ฐ์ํ ์ ์๋ ๋ชจ๋ ์๋ฌ์ ๋ํ ์ฒ๋ฆฌ๋ฅผ ํ๊ณ ์ถ์๊ธฐ์ ์ ์ ํ๋ค. ์๋๋ ์ฝ์์ฐฝ์ ๋จ์ํ ์ถ๋ ฅ์ด ์๋ ํน์ case์ ๋ฐ๋ฅธ ์๋ฆผ or ๋ค๋ฅธ ํ๋ฉด์ผ๋ก ์ด๋ํด ๋ค์ ํน์ ๋ก์ง์ ์ํํ๋ ๋ก์ง์ ๊ตฌํํด์ผ ํ๋ค. ๋ฐฉํ๋.. ํ ๊ฒ์ด๋ค.
๊ทธ๋์ LoginController์ input, output publisher์ failureํ์ ์ ์ ๋ถ LoginViewModelErrorType๊ฐ ๋๋ค. ( ์ ๋ค๋ฆญ๊ฐ๋ค์ typealias๋ก ๋ฐ๊พธ๋ฉด ์ผ๋ง๋ ์ข์๊น ์ฐพ์๋ดค๋๋ฐ ์์๋ค ใ ใ ,,)
// ์ฌ๋ด
ํ๊ฐ์ง ๋๋์ ์ c์ธ์ด๋ก ํ ํธ๋ฆฌ์ค ๋ง๋ค์์ ๋ ๊ฐ๊ฐ์ enum์ ์ซ์์ ๊ฐ๋ง ๊ฐ์ ์? ์์๋๋ฐ swift๋ ํน์ case์์ฒด๋ฅผ string์ผ๋ก๋ฐํํ๊ฑฐ๋ ๊ฐ๊ฐ์ case์ ๋ํด ํน์ ํ์ ์ผ๋ก ๋ฐํํ๋๊ฒ ์ง์ง ๋งค๋ ฅ์ธ๋ฏ ํ๋ค.
- LoginViewModelInput
๋ค์์ LoginController์์ ๋ฐ์ํ ์ ์๋ ์ด๋ฒคํธ๋ค์ struct๋ก ์ ์ํ ๊ฒ์ด๋ค. ์ด ๊ฐ๊ฐ์ publisher๊ฐ publised๋ ๊ฒฝ์ฐ viewModel์์ ํน์ input์ publiser์ ๋ํ upstream publisher's operator chaining์ ์ํํ ํ output์ผ๋ก ๋ฐํํ๋ค.
- LoginViewModelInputCase
๋ค๋ฅธ ๊ฑด ์๋ค. ๊ฐ๊ฐ์ input's upstream publisher์ ๋ํ ๊ฒฐ๊ณผ ์ฒ๋ฆฌ๋ฅผ stream์ ํํ๋ก ์ฒ๋ฆฌํ๋ ์ค์ง์ ์ธ ํจ์๋ค์ด๋ค. ์ด๋ protocol๋ก ๊ตฌํํ ์ด์ ๋ ์ฝ๋๋ฅผ ๋ชจ๋ํ ์์ผ transform(input:)ํจ์์ ๋ก์ง์ ์ฝ์ ๋ ์ดํดํ๊ธฐ ์ฝ๊ฒ ํ๊ธฐ ์ํด์ ๊ทธ๋ ๋ค. ๋ฌผ๋ก ๋ด๊ฐ ์๊ฐํ๊ฒ ์๋๋ค.(์ธ์ ๊ฐ ๋๋ง์ ๊ท์น์ ๊ผญ ๋ง๋ค ๊ฒ์ด๋ค... ๋์ค์ ์ผ๋ก ์ธ์ ๋ฐ์ผ๋ฉด ์ข๊ณ ,,) (๋งํฌ) ์๊ธฐ์์ ๋ณด์ด๋ ์ฑ ์ ์ ์๊ฐ ๋ง์ ์ธ์์ ์ฝ๋์ ํจ๊ป ํ๋ฉด์ ์์ ๋ ธํ์ฐ๋ค์ ๋ฐํ์ผ๋ก ํด๋ฆฐ ์ฝ๋๋ฅผ ๊ตฌํํ๋ค. ๋ ์ด ์ ์์ ๊ท์น์ด ์ ๋ง ๊ด์ฐฎ์ ๊ฒ ๊ฐ์ ํด๋ฆฐ์ฝ๋์ ๊ท์น์ ๋ฐ๋ผ ๊ตฌํ์ ํ๋ค!!!
- LoginControllerState
input -> transform(input:) -> LoginViewModelInput -> LoginViewModelInputCase ๋ฅผ ๊ฑฐ์น ํ์ ๊ฐ๊ฐ์ input์ ๋ํด์ Merge๋ฅผ ํตํด ์ต์ํ์ subscriber๋ ๊ฐ๊ฐ์ publiser ๊ฐ ์ฐ์ฐ์ ์ฒ๋ฆฌํ๋ฉด์์ ๊ฒฐ๊ณผ๋ฅผ ๋ฐํํ๋ค. ํฌ๊ฒ ๋ทฐ์ ์ํ ๋ณํ๋ 3๊ฐ์ง์ด๋ค. ์ฌ์ฉ์๊ฐ id, pw๋ฅผ ์ ๋ ฅํ๋์ง -> ๋๋ค ์ ๋ ฅํ์ ๊ฒฝ์ฐ loginButton enabled๋จ else disabled. none, ์คํ์ค์ธ ์ธ๋์ผ์ดํฐ ํด์ . ์ด ์ธ๊ฐ์ง๋ค!!
- LoginViewModelNetworkServiceType
๊ทธ ๋ฐ์ ๋ก๊ทธ์ธ ๋ฒํผ์ ํตํด ๋ก๊ทธ์ธ ํ ๋ ํ์ด์ด๋ฒ ์ด์ค์ ๋ฑ๋ก๋ ๊ณ์ ์ด ๋ง๋์ง ์ฌ๋ถ ํจ์๋ค. wwdc 2021์์ ์๊ฐ๋ async, await ๋น๋๊ธฐ ์ฒ๋ฆฌ๋ฅผ ๋์ ํ๋ค. ํคํค. ๋์ค์ ์ปด๋ฐ์ธ๊ณผ ๊ฒฐํฉ ํ ์ฝ๋๋ฅผ ๊ตฌํํ ๊ฒ์ด๋ค.
// ์ฌ๋ด2 ์ด๋ฒ ๋ฐฉํ๋ Swinject ์๋๋ฉด needle๋ฅผ ๊ณต๋ถํ ๊ฒ์ด๋ค. DI๋ ์ง๋ 3์ฃผ๊ฐ ์ปด๋ฐ์ธ ๊ณต๋ถ ๋ ๋จธ๋ฆฟ์์ผ๋ก๋ง ๊ณ์ ๋ ์ฌ๋ฆฌ๋ค๊ฐ ํ๊ต ๊ณผ์ ISS (๋งํฌ)์ฝ 2์ฃผ๊ฐ Instruction Set Simulator๋ฅผ ๊ตฌํ ํ ๋ ์์กด์ฑ์ด ์๋ ์ฝ๋๋ฅผ ๋์
ํ๊ณ ์ ์ด์ฌํ ๋
ธ๋ ฅํ๋ค. iss ํ๋ก์ ํธ ๋๋ ํ ๊ฐ๋
์ผ๋ก๋ง ์์งํ๊ณ ์๋ Combine์ ์ ์ฉํ๊ณ ์๋๋ฐ protocol ๊ด๋ จ์ ๋์์ง ์๊ฒ ์ค์ํํธ์ ๋ฐ๋ก ๋์
ํ ์ ์์๋ค.
๊ทผ๋ฐ C++์ interface๊ฐ ์ง์์ด ์ ๋๋ค๋ ๊ฒ์ ์ง์ง ๋ชฐ๋๋ค. ๊ทธ๋์ define์ผ๋ก ์ ํ๊ธด ํ๋ค.. ๊ทผ๋ฐ ์์ ๊ฐ์ํจ์๋ฅผ ์ ์ธ ํด๋ ๊ฒฐ๊ตญ์ ์์๋ฐ๋ ํ์ ํด๋์ค์์ ๋ฐ๋์ ๊ตฌํํ ๋ .h ํ์ผ์ ์ฌ ์ ์ธ ํ .cpp ํ์ผ์์ ๊ตฌํํด์ผ ํ๋ค๋๊ฒ ๋๋ฌด ์์ฌ์ ๋ค. swift ์๋ค๋ฉด ์ค์ฒฉ ์ ์ธ์ด ์๋ ๊น๋ํ ์ฝ๋์์ ํ
๋ฐ.. ์๋ฌดํผ ISS๋ฅผ ๊ตฌํํ ๋์ DI๊ด๋ จ ๊ฐ๋
์ ์ ์ฉํ๋ ๊ฒ์ ๋์์ง ์์๋ค๋ ๋ง!!
3. LoginViewModel (LoginViewModel Link)
์ด๊ฑฐ๋ ์ง์ ์ฝ๋๋ก ๋ณด๋๊ฒ ์ข๊ฒ ๋ค.
์๋ ์ฝ๋ ๋ฆฌ๋ทฐ๋ฅผ ์ ๋ง ์ข์ํ๋ค. ๊ทธ๋ฆฌ๊ณ ์ฃผ์ ์ผ๋ก ๋ถ์ฐ์ค๋ช ํ๋ ๊ฒ์ ๊ต์ฅํ ์ข์ํ๋ค.. ํ๊ต ๊ต์๋๋ค์ด ์์ผ์ ์ต๊ด์ด ๋ ๊ฒ์ด ์๊ธฐ๋ ํ๋ค. ํด๋ฆฐ ์ฝ๋๋ฅผ ์ฝ์ผ๋ฉฐ ์ฝ๋๋ฅผ ์ง์ ์ฝ์ ๋ ํ๋ฒ์ ์ ์ ์๋๋ก ํ๋ ๋ค์ด๋ฐ์ด ๊ต์ฅํ ์ค์ํ๋ค๊ณ ํ๋ค. ๊ทธ๋์ ์์ฆ์ ์ฃผ์ ์ฒ๋ฆฌํ๋ ์ต๊ด ๋์ ๊นํ์ issue์ ๊ฐ๋ฐํ๋ค๊ฐ ๋๋ ๊ฒฝํ์ด๋ ๋ฌธ์ , ์๋ฌ๋ค์ ๋ฌธ์ํ?๋ก ์์ฑํ๊ณ ์๋ค. ๊ทผ๋ฐ ์ฝ๋๋ฆฌ๋ทฐ, ์ฃผ์ ์ต๊ด์ ์ ๋ง ์ข๋ค๊ณ ์๊ฐํ๋ค. ํ์ง๋ง ์ฃผ์ ์์ด ๋ช ๋ฒ๋ง์ ์ฝ๋๋ฅผ ์ดํดํ๋ ์ฝ๋๋ ์ ๋ง ํ๋ฃกํ ์ฝ๋์ด๊ณ ์ฃผ์์ด ํ์ ์์ง๋ง ์ค๊ฐ์ ๋ง์ฃผํ ์ํฉ, ๋ฐฐ์ด์ ๋ค์ ์ฃผ์์ด ์๋ issue์ ๊ธฐ๋กํ๋ ์๋ก ์ต๊ด์ ๋ค์ด๊ณ ์๋ค. ๊ทธ๋ฆฌ๊ณ ์ต๋ํ ๋ค์ด๋ฐ์ ์์ธํ ํด์ ์ฝ๋์ ํ๋ฆ์ ์ ํํ๊ฒ ํ์ ํ ์ ์๋๋ก ๊ตฌํํ๋ ๊ฒ์ด ๋ด ๋ชฉํ๋ค. +_+
๋ง์ฃผํ ์๋ฌ1
Combine์ publisher tryMap์ ์ฌ์ฉ์ failureํ์ ์ ๊ดํ ์๋ฌ.
tryMap์ ์ฌ์ฉํด error๋ฅผ ๋์ง ์ ์์ ๊ฒฝ์ฐ publisher์ failureํ์ ์ Never๊ฐ ๋๋ฉด ์๋๋ค. Merge -> ๊ทธ๋๋ก eraseToAnyPublisher()๋ก ๋ณํํ subscription์ ๊ฐ์ ๊ฒฝ์ฐ์ ํ์ ์ ๋ชจํธํจ ์๋ฌ๊ฐ ๋ฌ๋ค.
๋ง์ฃผํ ์๋ฌ2
failure ํ์ ์ด Never๊ฐ ์๋ ๊ฒฝ์ฐ
tryMap ์ any error ํ์ ๋ฐํํ๋ค. ๋ด๊ฐ ์ํ๋ publisher failure type์ ๋ง๊ฒ mapError๋ฅผ ์ฌ์ฉํด์ ์ปค์คํ errorํ์ ์ผ๋ก ์๋ฌ๋ฅผ ์บ์คํ ํด์ผ ํ๋ค. ๊ทธ๋์ผ subscriber์๊ฒ failure๋ฅผ ๋์ง ์ ์๋ค. publisher์ failureํ์ Never๊ฐ ์๋ ๊ฒฝ์ฐ์๋ map์ด ์๋ tryMap๊ฐ์ try ๊ธฐ๋ฅ์ด ๋ด๊ธด ์ฐ์ฐ์๋ฅผ ์ฌ์ฉํด์ผ ํ๋ค.
LoginController์ MainHomeTabViewController ๊ฐ์ ๋ธ๋ฆฌ๊ฒ์ดํธ๋ฅผ ์ด์ ์ ์์ฑํ๊ธฐ์ ์ด์ ๋ณต์กํ๋๋ฐ publisher์ input output์ ๋๊ฐ์ vc๋ฅผ ์ ๋ฌํจ์ผ๋ก ํด๊ฒฐํ๋ค.
๋ณต์ตํ ๊ฐ๋
A, B๋ ๊ฐ์ publisher๊ฐ ์๋ค๊ณ ๊ฐ์ .
let myZip = A.zip(B)
-
combineLatest
์๋ A๊ฐ publishํ๊ณ B๋ํ publishํด์ผ๋ง myZip์ด published๋๋ค. ์ฝ๊ฐ ์ปคํ๋๋? ํ ์์ด publish๋์ผ๋ง ๋น๋ก์myZip์ด ์๋ํ๋ค. ์ฝ๊ฐ ๋ด๊ฐ ๊ตฌํํ๋ id,pw๋๋ค ์ ๋ ฅํด์ผ๋ง ๋ก๊ทธ์ธ ๋ฒํผ ํด์ ๋๋ ๋ก์ง๊ณผ ๊ฐ์. ์๋๊บผ๋ ์ฝ๊ฐ password ๋๋ฒ ์ ๋ ฅํด์ ์๊ธฐ๊ฐ ์ ๋ ฅํ password ๋ง๋์ง ํ์ธํ ๋์ operator๋ผ๊ณ ๋ณด๋ฉด ๋ ๊ฑฐ๊ฐ๋ค. -
zip
์๋ A๊ฐ publishํ๊ณ B๊ฐ publishํด์ผ๋ง myZip์ด publised๋๋ค. ๊ทธ๋ฆฌ๊ณ A๊ฐ publish๋๋ฉด B์ ์ต์ ๊ฐ๊ณผ ํจ๊ป publish๋๋ค. ์ฆ ๋๋ค publishedํ ๊ฒฝ์ฐ์ A๋ง publishํด๋ B๋ ์ต์ publis ๊ฐ์ ์ฌ์ฌ์ฉํ๋ค. ๋ฐ๋์ ๊ฒฝ์ฐ๋ ๊ฐ๋ค.
์ถ๊ฐ, ๊ฐ์ ํด์ผํ ๊ธฐ๋ฅ
-
๊ฒ์ํ๊ธฐ ์ํด ์์น๋ฐ๋ฅผ ํด๋ฆญํ ๋ tableView reloadData๊ฐ ์คํ๋๋ค. ์ด๊ฒ์ ์์ ์ผํ๋ค.
-
LoginController ๋น๋ฐ๋ฒํธ ์ต์ ์ ๋ ฅ ๊ฐ์ ์ ํ
-
RegistrationController
๋ํ ํ ์ค์ ํ์๊ฐ์ ์ ์ฐจ์ฒ๋ผ ๋ง๋ค์ด ๋ณผ ๊ฒ์ด๋ค.
์๋ก ๋ฆฌํํฐ๋ง ํด์ผํ ๊ฒ
- Feed VC ๊ด๋ จ
- Upload VC ๊ด๋ จ
SearchController ๋ฅผ SearchViewModel๊ณผ ๋ถ๋ฆฌํ ๋ SearchViewModelProtocols ์๊ธฐ์์ ์ด ๋ ํจ์๋ฅผ ๊ผญ UserViewModel๋ก ๋ถ๋ฆฌํด์ผํ๋ค. SearchViewModel์ ์์กด์ ์ด๋ค.
์ํ์ด 6์ผ ๋จ์๋ค.. ์ ๋ง ํฐ์ผ๋ฌ๋ค.
๋๋์
์ฌ์ค ์๋ฌ ์ฒ๋ฆฌ๋ฅผ ๋จ์ํ ์ฝ์์ฐฝ์ ์ถ๋ ฅํ๋ค. ๋ฐฉํ์ด ๋ด๊ฒ ์จ๋ค๋ฉด ํน์ ์ํฉ์ ๋ง๋ ์๋ฆผ์ด๋ ์ค๋ฅ๋ฅผ ๋์ฒดํ ์ ์๋ ๋ก์ง์ ๊ตฌํํ ๊ฒ์ด๋ค.
(์ํ๋ง ๋๋๋ค๋ฉด ์ปด๋ฐ์ธ ์ฑ ์ผ๋ก ์ถ๊ฐ ๊ณต๋ถํ ํ ๋ฐ)
MVVM ๋ฆฌํํฐ๋ง ์ถ๊ฐ
ProfileHeaderView.
fetch Network ( async, await + combine)
MainHomeTabControler
์ถํ cache ๊ณต๋ถํด์ ์ด๋ฏธ์ง fetch ๊ฐ์ ํด์ผํจ.
ProfileHeader, ProfileHeaderViewModel ๋ฆฌํํฐ๋ง ์๋ฃ.
๊ฐ์ ํด์ผ ํ ๊ธฐ๋ฅ์ Network fetch ๊ด๋ จ ๋ถ๋ถ์ ์ผ๋ก ๋ฆฌํํฐ๋ง ํ๋ async/await ์ ์ฒด์ ์ผ๋ก ์ ์ฉ์ํค์ (์ฌ์ฉ์ ํ๋ก์ฐ, ํ๋ก์ ๊ด๋ จ๋ง ํ๋ฉด ๋จ. + actor์ ์ฉ.