/iOS_News_MVVM-C

๐Ÿ“ฐ ๋‰ด์Šค ๊ฒ€์ƒ‰ Rx with MVVM-C

Primary LanguageSwift

News App

Naver news ๊ฒ€์ƒ‰ API๋ฅผ ํ™œ์šฉํ•œ iOS ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜.

Description

  • ์ตœ์†Œ ํƒ€๊ฒŸ : iOS 14.5
  • CleanArchitecture + MVVM-C ํŒจํ„ด ์ ์šฉ
  • CoreData ํ”„๋ ˆ์ž„์›Œํฌ ์‚ฌ์šฉ์œผ๋กœ ์ฆ๊ฒจ์ฐพ๊ธฐ ๋ชฉ๋ก ์œ ์ง€
  • Storyboard๋ฅผ ํ™œ์šฉํ•˜์ง€ ์•Š๊ณ  ์ฝ”๋“œ๋กœ๋งŒ UI ๊ตฌ์„ฑ
  • Pagination ๊ตฌํ˜„
  • Unit Test ์ง„ํ–‰
  • ๊ฐœ๋ฐœ ๊ณต์ˆ˜

Feature

  • ๊ธฐ์‚ฌ ๊ฒ€์ƒ‰ ๋ทฐ
    • ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ๊ธฐ์‚ฌ ๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ
    • ํŽ˜์ด์ง€๋„ค์ด์…˜ - prefetching ๋ฐฉ์‹
    • refreshing ๊ธฐ๋Šฅ
  • ํƒœ๊ทธ ์„ค์ • ๋ทฐ
    • Compositional layout์„ ํ†ตํ•œ self-sizing CollectionView
    • delegate ํŒจํ„ด์„ ํ†ตํ•œ ๋ฐ์ดํ„ฐ ์ „๋‹ฌ
  • ๋””ํ…Œ์ผ ์›น ๋ทฐ
    • ์ฆ๊ฒจ์ฐพ๊ธฐ ์ถ”๊ฐ€/์ œ๊ฑฐ
    • ์•กํ‹ฐ๋น„ํ‹ฐ ์ธ๋””์ผ€์ดํ„ฐ
    • WebView ๊ธฐ๋ฐ˜ ์—ฐ๊ฒฐ
    • ๋งํฌ ํด๋ฆฝ๋ณด๋“œ ๋ณต์‚ฌ
  • ์Šคํฌ๋žฉ ๋ทฐ
    • ์Šคํฌ๋žฉ ๋ชฉ๋ก ๊ด€๋ฆฌ
    • ์Šคํฌ๋žฉ ๋ฐ์ดํ„ฐ ์ œ๊ฑฐ

Getting Start

Swift, MVVM+C, CI/CD, Unit Test, CoreData, WebKit, SnapKit, Alamofire, Toast-swift, TTGTagCollectionView, RxCocoa, RxSwift, RxTest

Issue & Reflection

1. Coordinator ๊ตฌ์„ฑ ๋ฐ Tabbar / Navigation ์—ฐ๊ฒฐ

์ฒ˜์Œ ์ฝ”๋””๋„ค์ดํ„ฐ ํŒจํ„ด์„ ๊ตฌ์„ฑํ•˜๋‹ค ๋ณด๋‹ˆ ์ดํ•ดํ•˜๋Š”๋ฐ ๊ฝค ์‹œ๊ฐ„์ด ๋“ค์—ˆ๋‹ค. ์ƒ์„ฑ์ž ์ฃผ์ž…์„ ํ†ตํ•ด navigationController๋ฅผ ์ฃผ์ž…๋ฐ›๊ณ , ํ™”๋ฉด์ „ํ™˜ ์‹œ ํ•ด๋‹น navigationController๊ฐ€ ๋‹ค์Œ ํ™”๋ฉด์„ push ํ•˜๋„๋ก ๊ตฌ์„ฑํ–ˆ๋‹ค. ํ™•์‹คํžˆ ์ง์ ‘์ ์ธ ViewModel์—์„œ์˜ ViewController ์ƒ์„ฑ๊ณผ DI๋ฅผ ํ”ผํ•˜๊ณ  Container๋ฅผ ํ†ตํ•ด์„œ ํ•˜๋‹ค๋ณด๋‹ˆ View ์ž์ฒด์—์„œ๋Š” Model์— ๋Œ€ํ•ด์„œ ๋”์šฑ ์•Œ ์ˆ˜ ์—†๋„๋ก ๊ตฌ์„ฑํ–ˆ๋‹ค๋Š” ์ ์—์„  ๋งŒ์กฑ์ด๋‹ค. ์กฐ๊ธˆ ์˜๋ฌธ์ ์€ ํ™”๋ฉด์ „ํ™˜ ๋™์ž‘์„ ํด๋กœ์ € ํƒ€์ž…์œผ๋กœ actions์— ์ €์žฅํ•˜๊ณ , actions๋ฅผ ViewModel์˜ init ์‹œ ์ฃผ์ž…ํ•˜๋Š” ๋ฐฉ๋ฒ•๋„ ๊ณ ๋ คํ•ด๋ด์•ผ ํ•  ๊ฑฐ ๊ฐ™๋‹ค.

2. Test Coverage

ViewModel์˜ unit test์— ์žˆ์–ด์„œ ์ตœ๋Œ€ํ•œ coverage๋ฅผ ๋งŒ์กฑ์‹œํ‚ฌ ๊ฒƒ์„ ๊ณ ๋ คํ•˜๊ณ  ํ•„์š”์— ๋”ฐ๋ผ BDD์ฒ˜๋Ÿผ ์กฐ๊ฑด ๋ถ„๊ธฐ์— ๋Œ€ํ•œ ์ฒ˜๋ฆฌ๋ฅผ ํ†ตํ•˜์—ฌ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค ๋ถ„๋ฆฌํ–ˆ๋‹ค. ํ…Œ์ŠคํŠธ ๊ตฌ์„ฑ์— ์žˆ์–ด์„œ MVP ํŒจํ„ด์˜ Presenter์™€ ๋น„๊ตํ•  ๊ฒฝ์šฐ ์ปค๋ฒ„๋ฆฌ์ง€๋Š” ์ƒ๋‹นํžˆ ๋†’์„ ์ˆ˜ ๋ฐ–์— ์—†์—ˆ๋‹ค. ํ•˜์ง€๋งŒ, In/Out ํŒจํ„ด์— ๋Œ€ํ•œ ์ƒ๊ฐ๊ณผ ์ „๋ฐ˜์ ์ธ ์ปค๋ฒ„๋ฆฌ์ง€๊ฐ€ ์•„๋‹Œ ๊ตฌ์ฒด์ ์ธ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋ฅผ ์–ด๋–ป๊ฒŒ ๊ตฌ์„ฑํ•  ์ง€ ๊ณ ๋ฏผํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค.

Test์—์„œ In/Out์˜ ์ œํ•œ์ ๊ณผ ํ…Œ์ŠคํŠธ๋ฅผ ์‹œ๋‚˜๋ฆฌ์˜ค์— ๋”ฐ๋ผ ๋ถ„๋ฆฌ ๊ตฌ์„ฑํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•œ ๊ณ ๋ฏผ

๋‚ด๋ถ€ ๊ตฌ์กฐ์ฒด์— ์–ฝ๋งค์ด๋‹ค ๋ณด๋‹ˆ ์ „์ฒด์— ๋Œ€ํ•œ Default Input ์ด๋ฒคํŠธ๋ฅผ ๊ตฌ์„ฑํ•ด์„œ ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋ฅผ ๊ตฌ์„ฑํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค. ์ „๋ฐ˜์ ์ธ ํ…Œ์ŠคํŠธ ์‹œํ–‰์— ์žˆ์–ด์„œ๋Š” ์žฅ์ ๋„ ์žˆ์—ˆ์ง€๋งŒ ์ŠคํŠธ๋ฆผ์„ ๋ถ„๊ธฐ ๋‹จ์œ„ ๋ณ„๋กœ ์‹œ๋‚˜๋ฆฌ์˜ค์— ๋งž์ถฐ ๊ตฌ์„ฑํ•˜๋Š” ๋ฐ ์ œ์•ฝ์ด ์žˆ์—ˆ๋˜ ๊ฑฐ ๊ฐ™๋‹ค. ๋”ฐ๋ผ์„œ, ๋ถ„๋ฆฌํ•ด์„œ ์ ์ž˜ํ•œ ์ŠคํŠธ๋ฆผ์„ ์ „๋‹ฌํ•˜๋Š” ๋ฐฉ์‹๊ณผ ํ˜น์€ ๋ชจ๋ธ ๋‹จ์œ„๋ณ„๋กœ ๋ถ„๋ฆฌํ•ด์„œ ๋‚ด๋ถ€์˜ ๋กœ์ง๋งŒ ํ™•์ธํ•˜๋„๋ก ๊ตฌ์„ฑํ•˜๋Š” ๋ฐฉ๋ฒ•๋„ ์ƒ๊ฐํ•ด๋ด์•ผํ•œ๋‹ค.

์•„ํ‚คํ…์ณ์— ์ง‘์ค‘ํ•˜์—ฌ ์•„์ฃผ ์ž‘์€ ์ˆ˜์ค€์˜ ํ”„๋กœ์ ํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๋‹ค๋ณด๋‹ˆ ๋ณต์žกํ•œ ์ˆ˜์ค€์˜ ๊ธฐ๋Šฅ์ด ์—†์–ด์„œ ๊ตฌํ˜„์—๋Š” ํฐ ์–ด๋ ค์›€์ด ์—†์—ˆ๋‹ค. ํ•˜์ง€๋งŒ, ์‹ค์ œ ํ”„๋กœ์ ํŠธ์—์„  ๋ณต์žกํ•œ ๋ทฐ๋‚˜ ๊ธฐ๋Šฅ ๊ตฌํ˜„๊ณผ ๋ทฐ์˜ ์• ๋‹ˆ๋ฉ”์ด์…˜ ๊ฐ™์€ ๊ฒฝ์šฐ์—๋„ ๊ณ ๋ คํ•˜์—ฌ ๋‹ค์Œ ํ”„๋กœ์ ํŠธ ๋•Œ์—๋Š” ์กฐ๊ธˆ์€ ๋” ์‹ ๊ฒฝ ์จ๋ด์•ผ๊ฒ ๋‹ค. UI Test๋„ ํฌํ•จํ•ด์„œ ๋ง์ด๋‹ค.

3. Rx In/Out ํ˜•์‹์˜ ViewModel ๊ตฌ์„ฑ ๋ฐ ์ฒ˜๋ฆฌ

Signal, Drive ๋“ฑ์˜ Traits๋ฅผ ์ ๊ทน์ ์œผ๋กœ ํ™œ์šฉํ–ˆ๋‹ค. ํ•˜์ง€๋งŒ ๊ตฌ์„ฑํ•˜๋ฉด์„œ ์˜๋ฌธ์ด ์ƒ๊ธด ๊ฑด emit(), drive() ๋“ฑ onNext ๋‚ด๋ถ€์— ๋กœ์ง์„ ๊ตฌ์„ฑํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹Œ ์™ธ๋ถ€๋กœ ์ •๋ฆฌํ•ด์„œ ๋ถ„๋ฆฌํ•˜๋Š” ๊ฒƒ๋„ ๋ฐฉ๋ฒ•์ด๋ผ๊ณ  ์ƒ๊ฐํ–ˆ๋‹ค. ํ™•์‹คํžˆ ViewModel์ด ๋˜๋ฉด์„œ ์ฝ”๋“œ์˜ ์–‘์€ ์ค„์—ˆ์ง€๋งŒ, ์ฝ”๋“œ์˜ ํ๋ฆ„์„ ํ™•์‹คํžˆ ๋ณด๊ธฐ ์œ„ํ•ด์„œ ๋ง์ด๋‹ค. ๊ฐ€๋…์„ฑ์˜ ์ธก๋ฉด์—์„œ๋Š” ๊ต‰์žฅํžˆ ์˜ˆ์ „ ์ฝ”๋“œ์— ๋น„ํ•ด์„  ๊ฐœ์„ ์ด ๋งŽ์ด ๋˜์—ˆ๋‹ค.

Operator ํ™œ์šฉ์— ์žˆ์–ด์„œ๋„ ๊ธฐ์กด์— ํŠœํ† ๋ฆฌ์–ผ ๋ณด๋‹ค ์ œํ•œ์ ์ธ ์ˆ˜์ค€์— ์˜์กดํ•œ ๋Š๋‚Œ์ด ๊ฐ•ํ•ด์„œ UI ์ธํ„ฐ๋ ‰์…˜, ๋ฐ์ดํ„ฐ ํ•„ํ„ฐ ๋“ฑ ๊ตฌ์ฒด์ ์œผ๋กœ ํ™•์‹คํ•˜๊ฒŒ ์“ด ์˜ˆ์ œ๋“ค์„ ์ฐธ๊ณ ํ•˜์—ฌ ์‚ฌ์šฉ์„ฑ์— ๋Œ€ํ•ด์„œ ๊ณ ๋ฏผํ•ด๋ด์•ผ๊ฒ ๋‹ค. ๋‹จ์ˆœํ•œ ์‚ฌ์šฉ์ด ์•„๋‹Œ ํ•„ํ„ฐ, ํ•ฉ์„ฑ, take ๋“ฑ ๊ณ ๋ คํ•  ๋ถ€๋ถ„์„ ์˜ต์…˜์ฒ˜๋Ÿผ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์„ ๋•Œ ๊นŒ์ง€ ์—ฐ์Šต์„ ๋” ํ•ด๋ด์•ผํ•  ๊ฑฐ ๊ฐ™๋‹ค. ์ข‹์€ ๊ธฐ๋Šฅ์ด ๋งŽ์€๋ฐ ๋‹ค ๋ชป ์“ฐ๋Š” ๋Š๋‚Œ์ด ํฌ๋‹ค.

4. ์ƒํƒœ ๋ฐ ๋ฐ์ดํ„ฐ ์ „๋‹ฌ ๋ฐฉ์‹์— ๋Œ€ํ•œ ๊ณ ๋ฏผ

Delegate, Closure๋ฅผ ํ†ตํ•œ Scene ์‚ฌ์ด์—์„œ์˜ ์งง์€ ๊ฑฐ๋ฆฌ ์ˆ˜์ค€์˜ ์ „๋‹ฌ๊ณผ Notification์„ ํ†ตํ•œ ๊ธด ๊ฑฐ๋ฆฌ ์ˆ˜์ค€์˜ ์ „๋‹ฌ์— ๋Œ€ํ•œ ๊ณ ๋ฏผ์„ ํ•ญ์ƒ ํ•˜๋Š” ๊ฒƒ ๊ฐ™๋‹ค. ์ด๋ฒˆ์—๋Š” ํ—ค๋” ๋ถ€๋ถ„์„ ์„œ๋“œ ํŒŒํ‹ฐ์— ์˜์กดํ•˜๋ฉด์„œ ํ•˜๋‹ค ๋ณด๋‹ˆ ๊ฐ„๋‹จํ•œ ๋ฐ์ดํ„ฐ ์ „๋‹ฌ์— ์žˆ์–ด์„œ๋Š” delegate๋ฅผ ํ™œ์šฉํ•˜์˜€๋Š”๋ฐ ์“ฐ๋ฉด์„œ ์ˆœํ™˜ ์ฐธ์กฐ๋ฅผ ์ž˜ ๊ณ ๋ คํ•ด์•ผํ•  ๋“ฏ, Delegate๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ์— ํ•ญ์ƒ AnyObject๋ฅผ ์ž˜ ๋‹ฌ์ž! Delegate property๋Š” weak๋กœ ์„ ์–ธํ•ด์•ผ ํ•œ๋‹ค!

5. ๋„คํŠธ์›Œํฌ ๊ตฌํ˜„ ๋ฐ API ์ถ”์ƒํ™”

RxSwift๋ฅผ ํ™œ์šฉํ•˜์—ฌ ๋น„๋™๊ธฐ ์ž‘์—…์„ ์ฒ˜๋ฆฌํ–ˆ์ง€๋งŒ. ์„œ๋ฒ„์—์„œ ๋ฐ›์•„์˜จ ๋ฐ์ดํ„ฐ๋Š” ์ผ๋ฐ˜ ํƒ€์ž…์œผ๋กœ ์ „๋‹ฌํ•˜๊ณ  ํ•ด๋‹น ์ŠคํŠธ๋ฆผ์— ์ฃผ์ž…ํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ํ˜„์žฌ๋Š” ๊ตฌ์„ฑํ•ด์žˆ๋‹ค. ์•„๋ฌด๋ž˜๋„ ๋‹จ์ผ API ํ˜ธ์ถœ๊ณผ ๊ด€๋ จํ•˜์—ฌ ๊ฐ€๋ณ๊ฒŒ ์—ฌ๊ธฐ๊ณ  ์ž‘์—…์„ ์ง„ํ–‰ํ•œ ์ ์ด ๊ฐ€์žฅ ํฌ์ง€ ์•Š์„๊นŒ? ๊ทธ๋ž˜์„œ Moya ๋“ฑ์˜ ์„œ๋“œํŒŒํ‹ฐ๋ฅผ ์‚ฌ์šฉํ–ˆ์„ ๋•Œ๊ฐ€ ์ƒ๊ฐ์ด ๋‚ฌ๊ณ , ์—ฌ๋Ÿฌ ๊ฐœ์˜ API ํ˜ธ์ถœ ์‹œ์˜ ์“ฐ๋ ˆ๋“œ ๊ด€๋ฆฌ์™€ ํ•ด๋‹น case ๊ด€๋ฆฌ๋ฅผ ๋ณด๋‹ค ํŽธํ•  ๋ฐฉ๋ฒ•์„ ๊ณ ๋ฏผ ์ค‘์ด๋‹ค. ์—ด๊ฑฐํ˜•์ด ์•„๋‹Œ API๋งˆ๋‹ค ๋…๋ฆฝ์ ์ธ ๊ตฌ์กฐ์ฒด ํƒ€์ž…์œผ๋กœ ๊ด€๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ํ† ๋Œ€๋กœ ์ฝ”๋“œ์œ ์ง€ ๋ณด์ˆ˜๊ฐ€ ์šฉ์ดํ•˜๋„๋ก ๋ณด์™„ํ•ด๋ณด๋Š” ๋ฐฉ์‹๋„ ๋‚˜์˜์ง€ ์•Š์„ ๋“ฏ

5. ํด๋ฆฐ ์•„ํ‚คํ…์ณ

์ด๋ฒˆ ๊ณ„๊ธฐ๋กœ ์‹ค์ œ Robert C. Martin ์˜ Clean Arichitecture๋ฅผ ์ฝ์–ด๋ณด๊ณ  swift ํ˜•ํƒœ๋กœ ๊ตฌ์„ฑ๋œ ๊ฐ€์žฅ ์œ ๋ช…ํ•œ ์˜ˆ์ œ ๋ ˆํฌ๋ฅผ ์ฐธ๊ณ  ํ–ˆ๋‹ค. ํ™•์‹คํžˆ ๊ณ ๋ฏผํ•˜๋˜ ๋ถ€๋ถ„์— ๋Œ€ํ•œ ํ•ด์†Œ๋ฅผ ํ†ตํ•ด์„œ ์•„ํ‚คํ…์ณ ๊ตฌ์„ฑ์— ๋Œ€ํ•œ ์ƒ๊ฐ์„ ๋งŽ์ด ํ–ˆ๋‹ค. Ribs, Viper ๋“ฑ ๋ณด๋‹ค ๊ณ ๋‹จ๊ณ„๋กœ ์ •๋ฆฝ๋œ ์—ญํ• ์— ๋Œ€ํ•ด์„œ๋„ ํ•œ ๋ฒˆ ํŠœํ† ๋ฆฌ์–ผ์„ ์ง„ํ–‰ํ•ด์•ผํ•  ๋“ฏ

์•„์‰ฌ์šด ๋ถ€๋ถ„์€ ์ •๋ง๋กœ Clean ์ˆ˜์ค€์˜ ์ •๋„๊ฐ€ ์–ด๋””๊นŒ์ง€์ธ๊ฐ€? ํ•˜๋Š” ์˜๋ฌธ์ด๋‹ค. ๋ ˆ์ด์–ด์˜ ๋ถ„๋ฆฌ์™€ ์˜์—ญ ๊ตฌ์„ฑ์— ์žˆ์–ด์„œ ์•„์ง์€ ์ถ”์ƒํ™”๊ฐ€ ๋งŽ์ด ๋ถ€์กฑํ•˜๋‹ค. ์กฐ๊ธˆ ๋” ๋ฐ์ดํ„ฐ ํ”Œ๋กœ์šฐ์— ๋Œ€ํ•ด์„œ ๋” ๊นŠ์— ์ดํ•ดํ•˜๋Š”๊ฒŒ ์ค‘์š”ํ•œ ์‹œ์ ์ด๋‹ค.

ScreenShot

IMG_C017886C83C0-1