Google Books API๋ฅผ ํตํด ๋์ ๊ฒ์ ๊ฒฐ๊ณผ๋ฅผ ๋ชฉ๋ก/์์ธ ํ๋ฉด์ ๋ํ๋ ๋๋ค.
- Deployment Target : iOS 14.0
- Architecture : MVVM-C
- ๋ผ์ด๋ธ๋ฌ๋ฆฌ : RxSwift, SwiftLint / ์์กด์ฑ ๊ด๋ฆฌ๋๊ตฌ : SPM
- ์ฝ๋ฉ ์ปจ๋ฒค์ , ์ปค๋ฐ ์ปจ๋ฒค์ ๋ฑ ์์ธํ ๋ด์ฉ์ Wiki๋ฅผ ์ฐธ๊ณ ํด์ฃผ์ธ์.
BookFinder
โโโ App
โโโ Presentation
โ โโโ SearchListPage
โ โ โโโ View
โ โ โโโ ViewModel
โ โโโ DetailPage
โ โโโ View
โ โโโ ViewModel
โโโ Model
โโโ Network
โ โโโ Entities
โโโ Protocols
โโโ Extensions
โโโ Utilities
โโโ Resources
BookFinderTests
โโโ Mock
- Feature-1. ๋คํธ์ํฌ ๋ฐ ๋ชฉ๋ก ํ๋ฉด ๊ตฌํ
- Feature-2. ์์ธ ํ๋ฉด ๊ตฌํ
- ๋ณด์ํ ์
- ์ฌ์ฉ์๊ฐ ๊ฒ์ํ ๋์ ๋ฐ์ดํฐ๋ฅผ ์๋ฒ์์ ๋ฐ์์ ๋ชฉ๋ก์ผ๋ก ๋ํ๋ ๋๋ค.
URLSession
์ ํตํด ๋คํธ์ํฌ ํต์ ์ ๊ตฌํํ์ต๋๋ค. (MockURLSession์ ํตํ ํ ์คํธ ์คํ)SearchController
์ ๊ฒ์ํค์๋๋ฅผ ์ ๋ ฅํ ๋๋ง๋คCollectionView
๊ฐ ์ฆ์ ์ ๋ฐ์ดํธ๋ฉ๋๋ค.- ๋ชฉ๋ก ์ตํ๋จ์ผ๋ก Scrollํ๋ฉด ์๋ฒ์์ ๋ค์ ํ์ด์ง์ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์์ค๋๋ก
Pagination
์ ๊ตฌํํ์ต๋๋ค. - iPad ๋ฑ Wide Screen๊ณผ ๊ฐ๋ก/์ธ๋ก ๋ชจ๋ ์ ํ์ ๋์ํ๋
๋ฐ์ํ ๋์์ธ
์ ๊ตฌํํ์ต๋๋ค. (โ ์ถ๊ฐ ๊ตฌํ) ActivityIndicator
๋ฅผ ํตํด ๋ก๋ฉ ์ ๋๋ฉ์ด์ ์ ๋ณด์ฌ์ค๋๋ค. (โ ์ถ๊ฐ ๊ตฌํ)- ์ด๋ฏธ์ง
Cache
๋ฅผ ๊ตฌํํ์ต๋๋ค. (โ ์ถ๊ฐ ๊ตฌํ) JSONParserTests
,MockNetworkProviderTests
,NetworkProviderTests
๋ฅผ ํตํ ํ ์คํธ ๋ฅผ ์งํํ์ต๋๋ค. (โ ์ถ๊ฐ ๊ตฌํ)
*๋ฐฐ๊ฒฝ ๋ฐ ๋ฆฌ๋ทฐ ๋ ธํธ ๋ฑ์ ๊ด๋ จ PR "๊ฒ์ํค์๋๋ฅผ ์ ๋ ฅํ๋ฉด ์๋ฒ ๋ฐ์ดํฐ๋ฅผ ์ ๋ฌ๋ฐ์ ๋ชฉ๋ก ํ๋ฉด์ ๋ํ๋ ๋๋ค."๋ฅผ ์ฐธ๊ณ ํด์ฃผ์ธ์.
๋ชฉ๋ก ํ๋ฉด |
---|
![]() |
RxSwift
๋ฅผ ํ์ฉํ์ฌ ๋น๋๊ธฐ ์์
์ ์ฒ๋ฆฌํ์ต๋๋ค. ์๋ฒ์์ ๋ฐ์์จ ๋ฐ์ดํฐ๋ Observable
ํ์
์ผ๋ก ๋ฐํํ๊ณ , ViewModel์์ ViewController์ ์ ๋ฌ (Binding)ํ์ฌ ํ๋ฉด์ ๋ํ๋ด๋๋ก ๊ตฌํํ์ต๋๋ค. ์ด๋ ๋ฐ์ดํฐ๋ฅผ ํ๋ฉด์ ๋ํ๋ด๋ ์ต๋ง๋จ ์์ ์๋ง Subscribe
ํ์ฌ Stream์ด ๋๊ธฐ์ง ์๋ ๊ตฌ์กฐ๋ฅผ ์ ์งํ์ต๋๋ค.
๋ํ API๋ฅผ ์ด๊ฑฐํ
์ผ๋ก ๊ด๋ฆฌํ๋ ๊ฒฝ์ฐ, API๋ฅผ ์ถ๊ฐํ ๋๋ง๋ค ์๋ก์ด case๋ฅผ ์์ฑํ์ฌ ์ด๊ฑฐํ์ด ๋น๋ํด์ง๊ณ , ์ด๊ฑฐํ ๊ด๋ จ switch๋ฌธ์ ๋งค๋ฒ ์์ ํด์ผ ํ๋ ๋ฒ๊ฑฐ๋ก์์ด ์์์ต๋๋ค. ๋ฐ๋ผ์ API๋ง๋ค ๋
๋ฆฝ์ ์ธ ๊ตฌ์กฐ์ฒด
ํ์
์ผ๋ก ๊ด๋ฆฌ๋๋๋ก ๋ณ๊ฒฝํ๊ณ , URL ํ๋กํผํฐ ์ธ์๋ HttpMethod
ํ๋กํผํฐ๋ฅผ ์ถ๊ฐํ APIProtocol ํ์
์ ์ฑํํ๋๋ก ๊ฐ์ ํ์ต๋๋ค. ์ด๋ก์จ ์ฝ๋์ ์ง ๋ณด์๊ฐ ์ฉ์ดํ๋ฉฐ, ํ์
์ ๊ฐ์ ๋ด๋นํ API ๊ตฌ์กฐ์ฒด ํ์
๋ง ๊ด๋ฆฌํ๋ฉด ๋๊ธฐ ๋๋ฌธ์ ์ถฉ๋์ ๋ฐฉ์งํ ์ ์์ต๋๋ค.
์๋์ ๋ชฉ์ ์ ์ํด MockURLSession
์ ๊ตฌํํ์ต๋๋ค.
- ์ค์ ์๋ฒ์ ํต์ ํ ๊ฒฝ์ฐ ํ ์คํธ์ ์๋๊ฐ ๋๋ ค์ง
- ์ธํฐ๋ท ์ฐ๊ฒฐ์ํ์ ๋ฐ๋ผ ํ ์คํธ ๊ฒฐ๊ณผ๊ฐ ๋ฌ๋ผ์ง๋ฏ๋ก ํ ์คํธ ์ ๋ขฐ๋๊ฐ ๋จ์ด์ง
- ์ค์ ์๋ฒ์ ํต์ ์ ํ๋ฉฐ ์๋ฒ์ ํ ์คํธ ๋ฐ์ดํฐ๊ฐ ๋ถํ์ํ๊ฒ ์ ๋ก๋๋๋ Side-Effect๊ฐ ๋ฐ์ํจ
Coordinator
๋ฅผ ํตํด ์์กด์ฑ ์ฃผ์
์ ๊ด๋ฆฌํ๊ณ , ํ๋ฉด์ ํ ์ญํ ์ ์ ๋ดํ๋๋ก ํ์ต๋๋ค. ์ด๋ฅผ ์ํด navigationController
๋ฅผ ์์ฑ์ ์ฃผ์
์ผ๋ก ํ์ ChildCoordinator์ ์ ๋ฌํ๊ณ , ํ๋ฉด์ ํ ์ ํด๋น navigationController
๊ฐ ๋ค์ ํ๋ฉด์ push ํ๋๋ก ํ์ต๋๋ค.
์ฌ์ฉ์๊ฐ ๊ฒ์ ํค์๋๋ฅผ ์
๋ ฅํ ๋๋ง๋ค ๋ชฉ๋ก์ ์
๋ฐ์ดํธํ๋๋ก ๊ตฌํํ๊ธฐ ์ํด DiffableDataSource
๋ฅผ ํ์ฉํ์ต๋๋ค. reloadData()
๋ฅผ ํธ์ถํ ํ์๊ฐ ์์ผ๋ฏ๋ก CollectionView Cell์ด ์
๋ฐ์ดํธ๋ ๋๋ง๋ค ์ ๋๋ฉ์ด์
์ด ์ ์ฉ๋์ด UX ์ธก๋ฉด์์ ์ ๋ฆฌํ๋ค๊ณ ํ๋จํ์ต๋๋ค.
๋ํ RxSwift๋ฅผ ํตํด ViewModel๊ณผ ViewController๋ฅผ Binding ์์ผ ์ญํ ์ ๋ถ๋ฆฌํ์ต๋๋ค. ์๋ฅผ ๋ค์ด ์ฌ์ฉ์๊ฐ ๋ชฉ๋กํ๋ฉด์์ ์คํฌ๋กค์ ์ตํ๋จ์ผ๋ก ๋ด๋ฆฌ๋ฉด, ViewModel
์ ์๋ฒ๋ฅผ ํตํด ๋ฐ์ดํฐ๋ฅผ ์
๋ฐ์ดํธํ๊ณ , ViewController
๋ Snapshot์ applyํ์ฌ ํ๋ฉด์ ๋ค์ ๊ทธ๋ฆฌ๋๋ก ํ์ต๋๋ค.
์๋ฒ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์์ค๋ ๋์ ๋ก๋ฉ ์ ๋๋ฉ์ด์
์ ๋ํ๋ด๊ธฐ ์ํด ActivityIndicator
๋ฅผ ๊ตฌํํ์ต๋๋ค. ํ์ง๋ง ActivityIndicator๋ฅผ ๋ณด์ฌ์ฃผ๋ ์์ (ex. ๋ชฉ๋ก ์ตํ๋จ์ผ๋ก Scrollํ์ฌ ๋ค์ ํ์ด์ง๋ฅผ ๋ํ๋ผ ์์ ๋ฑ)์ ViewModel
์ด ์๊ณ ์์ผ๋ฏ๋ก ViewController
๋ฅผ delegate
๋ก ์ค์ ํ์ต๋๋ค.
๋ํ ViewModel์์ delegate๋ฅผ ํธ์ถํ๋ ๋ฐฉ์์ผ๋ก ActivityIndicator์ ์ ๊ทผํ ๊ฒฝ์ฐ Main ์ค๋ ๋
๋ฅผ ํตํด ํ๋ฉด์ ๋ํ๋ด์ผ ํ๋ฏ๋ก DispatchQueue
๋ฅผ ํ์ฉํ์ต๋๋ค.
UISearchController๋ฅผ ํ์ฉํ์ฌ NavigationBar ๋ด๋ถ์ ๊ฒ์์ฐฝ์ด ์์นํ๋๋ก ํ์ต๋๋ค. SearchBar
๋์ UISearchController
์ ์ฌ์ฉํ ์ด์ ๋ ๋ชฉ๋ก์ Scrollํ ๋ ์๋์ผ๋ก ๊ฒ์์ฐฝ์ ์จ๊ธฐ๊ณ , ๊ฒ์์ฐฝ์ ํ
์คํธ๋ฅผ ์
๋ ฅํ ๋ ์๋์ผ๋ก Navigation Title์ ์จ๊ธฐ๋ ๋ฑ ์ฌ์ฉ์์๊ฒ ๋ณด๋ค ์ง๊ด์ ์ธ UX๋ฅผ ๊ตฌํํ ์ ์๊ธฐ ๋๋ฌธ์
๋๋ค.
SearchBar์ ๊ณต๋ฐฑ๋ง ์ ๋ ฅํ๋ฉด ํ๋ฉด์ด ์ ๋ฐ์ดํธ๋์ง ์๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ต๋๋ค. ์์ธ์ API query์ ๋น๋ฌธ์ ๋๋ ๊ณต๋ฐฑ๋ง ์ ๋ฌํ๋ฉด ์๋ฒ์์ error๋ฅผ ๋ฐํํ์ฌ stream์ด ๋๊ธฐ๋ ๊ฒ์ผ๋ก ํ์ ํ์ต๋๋ค.
๋ฐ๋ผ์ SearchText๊ฐ ๋น๋ฌธ์์ด๊ฑฐ๋ ๊ณต๋ฐฑ์ผ๋ก๋ง ๊ตฌ์ฑ๋ ๋ฌธ์์ด์ธ ๊ฒฝ์ฐ, ์๋ฒ์ ๋ฐ์ดํฐ๋ฅผ ์์ฒญํ์ง ์๋๋ก ์์ธ ์ฒ๋ฆฌํ์ต๋๋ค.
JSON Parsing
ํ
์คํธ๋ฅผ ํ ๋, Bundle.main.path
๋ฅผ ํตํด Mock ๋ฐ์ดํฐ์ ์ ๊ทผํ๋๋ก ํ๋๋ฐ, path์ nil์ด ๋ฐํ๋๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ต๋๋ค. LLDB ํ์ธ ๊ฒฐ๊ณผ Mock ๋ฐ์ดํฐ ํ์ผ์ด ํฌํจ๋ Bundle์ BookFinderTests.xctest
์ด๋ฉฐ, ํ
์คํธ ์ฝ๋๋ฅผ ์คํํ๋ ์ฃผ์ฒด๋ App Bundle
์์ ํ์
ํ์ต๋๋ค.
๋ฐ๋ผ์ ํ์ฌ executable์ Bundle ๊ฐ์ฒด๋ฅผ ๋ฐํํ๋ Bundle.main
(์ฆ, App Bundle)์ด ์๋๋ผ, ํ
์คํธ ์ฝ๋๋ฅผ ์คํํ๋ ์ฃผ์ฒด๋ฅผ ๊ฐ๋ฅดํค๋ Bundle(for: type(of: self))
(์ฆ, XCTests Bundle)๋ก path๋ฅผ ์์ ํ์ฌ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ์ต๋๋ค.
3. Wide Screen ๋์ ์ CollectionView Group์ itemCount๋ฅผ ์กฐ์ ํ๋ฉด์ Layout์ด ๊นจ์ง๋ ๋ฌธ์
iPad ๋ฑ Wide Screen
์ ๋์ํ๊ธฐ ์ํด Screen Width๊ฐ 1000์ด์์ธ ๊ฒฝ์ฐ CollectionView Group
์ itemCount
๋ฅผ 2๋ก ์กฐ์ ํ ์ดํ ์๋์ฒ๋ผ Scrollํ ๋ imageView๊ฐ ์ปค์ง๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ต๋๋ค.
Layout์ด ๊นจ์ง๋ ํ์ |
---|
![]() |
CollectionView CompositionalLayout์์ estimatedHeight๋ฅผ ์ฌ์ฉํ๋๋ฐ, Cell Constraints๋ฅผ ์ค์ ํ ๋ Cell์ ํฌ๊ธฐ์ ๋์ผํ๊ฒ Horizontal StackView
๋ฅผ ๋ฃ๊ณ , imageView width๋ฅผ stackView width * 0.2
์ผ๋ก ์ค์ ํ์๋๋ฐ, ์ด ์กฐ๊ฑด๋ง์ผ๋ก๋ imageView์ height๊ฐ ๋ช
ํํ์ง ์์์ ๋ฐ์ํ๋ ๋ฌธ์ ๋ผ๊ณ ํ์
ํ์ต๋๋ค.
๋ฐ๋ผ์ estimatedHeight๋ฅผ ์ ์งํ ์ฑ๋ก ๊ธฐ์กด Horizontal StackView๋ฅผ ์ญ์ ํ๊ณ , imageView์ height:width ๋น์จ
์ ์ถ๊ฐํ์ฌ ์๋์ฒ๋ผ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ์ต๋๋ค. (Wide Mode์์ group๋น itemCount๊ฐ 2์ด ๋๋ฉด์ Cell width๊ฐ 1/2์ด ๋๊ธฐ ๋๋ฌธ์ imageView width๊ฐ Cell width์ 0.2๊ฐ ์๋ 0.4๊ฐ ๋๋๋ก ์์
์ ์ถ๊ฐํ์ต๋๋ค.)
iPhone8 | iPhone13 Pro Max | iPad Pro 12.9 |
---|---|---|
![]() |
![]() |
![]() |
CompositionalLayout์์ estimatedHeight
๋ฅผ ์ฌ์ฉํ ๋, iOS 15.0 ์ด์ 15.3 ์ดํ ๋ฒ์ ์์ crash๊ฐ ๋ฐ์ํ์ต๋๋ค. ์ด์ ๋์ํ๊ธฐ ์ํด ์ฌ์ฉ์์ ๊ธฐ๊ธฐ ๋ฒ์ ์ด iOS 15.0~15.3์ธ ๊ฒฝ์ฐ Alert๋ฅผ ๋์ ์ฌ์ฉ์์๊ฒ ์
๋ฐ์ดํธ๋ฅผ ๊ถํ๋๋ก ๊ตฌํํ์ต๋๋ค.
subtitle, publishedData ๋ฑ ์ผ๋ถ ๋ฐ์ดํฐ๊ฐ ๋๋ฝ๋ ๊ฒฝ์ฐ๊ฐ ๋น๋ฒํ์ฌ JSON Parsing
์๋ฌ๋ฅผ ๋ฐฉ์งํ๊ธฐ ์ํด DTO ํ์
์ ๋ชจ๋ ํ๋กํผํฐ๋ฅผ ์ต์
๋ ํ์
์ผ๋ก ์ง์ ํ์ต๋๋ค.
๋ง์ฐฌ๊ฐ์ง๋ก ์ด๋ฏธ์ง URL ํฌํจ ์ ๋ฌด๋ ๋ฐ์ดํฐ๋ง๋ค ๋ค๋ฅด๊ธฐ ๋๋ฌธ์ URL์ด ์๋ ๊ฒฝ์ฐ ์์์ SF Symbol
์ด๋ฏธ์ง๋ฅผ ๋ํ๋ด์ด ์๋ฌ ํ๋ฉด์ผ๋ก ์ธ์๋๋ ๊ฒ์ ๋ฐฉ์งํ์ต๋๋ค.
- ๊ฒ์ ๋ชฉ๋ก์ ๋์ Cell์ ํญํ๋ฉด ์์ธ ํ๋ฉด์ผ๋ก ์ด๋ํฉ๋๋ค. (โ ์ถ๊ฐ ๊ตฌํ)
*๋ฐฐ๊ฒฝ ๋ฐ ๊ตฌ์ฒด์ ์ธ ์์ ๋ด์ฉ์ ๊ด๋ จ PR "๋ชฉ๋ก ํ๋ฉด์์ ๋์ Cell์ ํญํ๋ฉด ์์ธ ํ๋ฉด์ ๋ํ๋ ๋๋ค."๋ฅผ ์ฐธ๊ณ ํด์ฃผ์ธ์.
์์ธ ํ๋ฉด |
---|
![]() |
์์ธ ํ๋ฉด์ ๋ชฉ๋ก ํ๋ฉด๊ณผ ์ฐ๊ฒฐ๋๋ฏ๋ก SeachListCoordinator
์ childCoordinators์ DetailCoordinator
๋ฅผ ์ถ๊ฐํ์ต๋๋ค. ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ๋ฅผ ์ํด ์์ธ ํ๋ฉด์ด ํ๋ฉด์์ pop๋ ๋, ViewModel์ deinit์์ Coordinator๊ฐ finish๋๋๋ก ์ค์ ํ์ต๋๋ค. SeachListCoordinator๋ฅผ delegate๋ก ์ง์ ํ๊ณ , removeFromChildCoordinators(coordinator:)
๋ฅผ ํธ์ถํ์ฌ DetailCoordinator๋ฅผ childCoordinator์์ ์ ๊ฑฐํ์ต๋๋ค.
Debug Memory Graph
๋ฅผ ํตํ ๋๋ฒ๊น
์ผ๋ก Coordinator/ViewModel ๋ฑ์ด ๋ฉ๋ชจ๋ฆฌ์์ ์ ์์ ์ผ๋ก ํด์ ๋๋์ง ํ์ธํ์ต๋๋ค.
์์ธ ํ๋ฉด์ด pop ๋๋ ๊ฒฝ์ฐ, ๊ฒ์์ฐฝ์์ ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ์ง ์์๋ updateSearchResults
๋ฉ์๋๊ฐ ํธ์ถ๋ฉ๋๋ค. ์ด๋ก ์ธํด ์๋ฒ ๋ฐ์ดํฐ๋ฅผ ์ฌ์์ฒญํ๋ฉด์ Scroll์ด ๋งจ ์๋ก ์ด๋ํ์ฌ UX์ ์
์ํฅ์ ์ฃผ๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ต๋๋ค.
์ด๋ฅผ ๋ฐฉ์งํ๊ณ ์ ViewModel์์ searchText๋ฅผ ์ ๋ฌ๋ฐ์ ๋ distinctUntilChanged
operator๋ฅผ ์ฌ์ฉํ์ฌ ๋์ผํ ๋ฌธ์์ด์ด ์ ๋ฌ๋๋ฉด ํํฐ๋ง๋๋๋ก ์ฒ๋ฆฌํ์ต๋๋ค.
Horizontal Scroll์ด ๋๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ฌ ScrollView์ ContentView Width
๊ฐ ํ๋ฉด ํฌ๊ธฐ์ ๋์ผํ๋๋ก ์ค์ ํ์ต๋๋ค. ๊ทธ๋ฆฌ๊ณ TextView
์ ํ
์คํธ๊ฐ ์ฑ์์ง๋ฉด์ Scroll์ด ์๋๋ก ๋ด๋ ค๊ฐ๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ฌ ๋ค์ Scroll์ ๋งจ ์๋ก ์ฌ๋ฆฌ๋ ์์
์ ์ถ๊ฐํ์ต๋๋ค.
-
iPad ๋ฑ Wide Screen์ ๋์ํ๊ธฐ ์ํด Compositional Layout์ columnCount ๊ฐ์ ์ฌ์ค์ ํ ์์ ์ ๋๋ค.(์๋ฃ) - Quick/Numble์ ํ์ฉํ์ฌ ViewModel ํ ์คํธ ์ฝ๋๋ฅผ ์ถ๊ฐํ ์์ ์ ๋๋ค.
- Clean Swift๋ฅผ ์คํฐ๋ํ๊ณ ์ ์ฉํด๋ณผ ์์ ์ ๋๋ค.