Naver news ๊ฒ์ API๋ฅผ ํ์ฉํ iOS ์ดํ๋ฆฌ์ผ์ด์ .
- ์ต์ ํ๊ฒ : iOS 14.5
- CleanArchitecture + MVP ํจํด ์ ์ฉ
- CoreData ํ๋ ์์ํฌ ์ฌ์ฉ์ผ๋ก ์ฆ๊ฒจ์ฐพ๊ธฐ ๋ชฉ๋ก ์ ์ง
- Storyboard๋ฅผ ํ์ฉํ์ง ์๊ณ ์ฝ๋๋ก๋ง UI ๊ตฌ์ฑ
- Pagination ๊ตฌํ
- Unit Test ์งํ
- ๊ฐ๋ฐ ๊ณต์
- ๊ธฐ์ฌ ๊ฒ์ ๋ทฐ
- ์นดํ ๊ณ ๋ฆฌ๋ณ ๊ธฐ์ฌ ๊ฒ์ ๊ธฐ๋ฅ
- ํ์ด์ง๋ค์ด์ - prefetching ๋ฐฉ์
- refreshing ๊ธฐ๋ฅ
- ํ๊ทธ ์ค์ ๋ทฐ
- Compositional layout์ ํตํ self-sizing CollectionView
- delegate ํจํด์ ํตํ ๋ฐ์ดํฐ ์ ๋ฌ
- ๋ํ
์ผ ์น ๋ทฐ
- ์คํฌ๋ฉ ์ถ๊ฐ/์ ๊ฑฐ
- ์กํฐ๋นํฐ ์ธ๋์ผ์ดํฐ
- WebView ๊ธฐ๋ฐ ์ฐ๊ฒฐ
- ๋งํฌ ํด๋ฆฝ๋ณด๋ ๋ณต์ฌ
- ์คํฌ๋ฉ ๋ทฐ
- ์คํฌ๋ฉ ๋ชฉ๋ก ๊ด๋ฆฌ
- ์คํฌ๋ฉ ๋ฐ์ดํฐ ์ ๊ฑฐ
Swift, MVP, CI/CD, Unit Test, CoreData, WebKit, SnapKit, Alamofire, Toast-swift, TTGTagCollectionView
1. TableView reloadData()์์ Section Header ์์ญ ๋ฆฌ๋ก๋๋ก ์ธํด์ Tag๊ฐ ๊ณ์ ์ถ๊ฐ์ ์ผ๋ก ์์ฑ๋๋ ์ํฉ ๋ฐ์
Header์ ๋ด๋ถ ๋ฉ์๋ setup() ์คํ์ ์์ด์ delegate๋ ์ ์งํ๊ณ ์ถ๊ฐ์ ์ธ ์์ฑ์ ๋ถ๊ธฐ์ฒ๋ฆฌ๋ฅผ ํตํด ํด๊ฒฐ, ์ด์ tags์ ์์ ๋ด String ๊ฐ์ ์ ๊ทผํ๋ ๊ฒ์ด ์ ํ์ ์ด๋ผ ๊ฐ๋ฅํ ์ ๊ทผํ์ฌ ํด๋น ๋ฐฐ์ด์ ์์ฑํ์ฌ ๋น๊ต ํ ํ๊ทธ ์ค์ ํ๋๋ก ๋ถ๊ธฐ ์ฒ๋ฆฌ
func setup(tags: [String], delegate: NewsListViewHeaderDelegate) {
self.tags = tags
self.delegate = delegate
contentView.backgroundColor = .systemBackground
setupLayout()
let prevTags = tagCollectionView.allTags().compactMap { tag in
"\(tag.content)"
.replacingOccurrences(
of: "<TTGTextTagStringContent: self.text=",
with: ""
)
.replacingOccurrences(
of: ">",
with: ""
)
}
if prevTags != tags {
tagCollectionView.removeAllTags()
setupTagCollectionView()
}
}
Presenter์ unit test์ ์์ด์ ์ต๋ํ coverage๋ฅผ ๋ง์กฑ์ํฌ ๊ฒ์ ๊ณ ๋ คํ๊ณ ํ์์ ๋ฐ๋ผ BDD์ฒ๋ผ ์กฐ๊ฑด ๋ถ๊ธฐ์ ๋ํ ์ฒ๋ฆฌ๋ฅผ ํตํ์ฌ ํ ์คํธ ์ผ์ด์ค ๋ถ๋ฆฌ
๊ธฐ์กด์๋ .xcconfig ํ์ฅ์์ ํ์ผ๋ก ๊ด๋ฆฌํ๋ฉด์ API Key๋ฅผ ๊ด๋ฆฌํ์๋ค. ํ์ง๋ง, CI ํ๊ฒฝ์ ๊ตฌ์ฑํ๊ณ ๋ฆฌ๋ชจํธ ๋น๋ ๋๋ ํ๊ฒฝ์์ ํด๋น ํ์ผ์ .gitignore์ ์ํด์ ์ ๋ก๋ฉ์ด ์ ํ๋๊ณ ๊ฒฐ๊ตญ์๋ ๋น๋๊ฐ ๋์ง ์๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ค.
์ด๋ด ๊ฒฝ์ฐ, fastLane, github action, jenkins ๋ฑ ๋ค๋ฅธ ์๋ฃจ์ ๋ค๋ secrets์ env var๋ฅผ ์ ๊ณตํ๋ฉด์ ๋ด๋ถ์์ ๋ณด์์ ์ธ ๊ฒฝ์ฐ๋ฅผ ํด๊ฒฐ ํ ์ ์๋๋ก ๊ตฌ์ฑ์ด ๊ฐ๋ฅํ๋ค๊ณ ํ๋ค.
๋ฐ๋ผ์, ๊ธฐ์กด์ ํ์ฉํ๋ .xcconfig๋ฅผ ๊ฑท์ด๋ด๊ณ ๊ฐ๋จํ๊ฒ bitrise์์์ secrets๋ฅผ ๋ณ์๋ก ์ค์ ํ๊ณ ํด๋น ๊ฐ์ ๊ฐ์ ธ์ค๋ ๋ฐฉ์์ผ๋ก ๋ฐ๊พธ์ด ์ฐ์ ์ repo์ commit ํด๋์๊ณ remote build ์ ๋ฌธ์ ์์ด ๊ตฌ์ฑํ ์ ์์๋ค.
- ํ๊ฒฝ ๋ณ์ ์ค์ ๋ฐ Secret ๊ตฌ์ฑ์ ํตํ ๊ด๋ฆฌ ๋ฐฉ์ ๋ ํผ๋ฐ์ค 1
- ํ๊ฒฝ ๋ณ์ ์ค์ ๋ฐ Secret ๊ตฌ์ฑ์ ํตํ ๊ด๋ฆฌ ๋ฐฉ์ ๋ ํผ๋ฐ์ค 2
- ํ๊ฒฝ ๋ณ์ ์ค์ ๋ฐ Secret ๊ตฌ์ฑ์ ํตํ ๊ด๋ฆฌ ๋ฐฉ์ ๋ ํผ๋ฐ์ค 3
- ํ๊ฒฝ ๋ณ์ ์ค์ ๋ฐ Secret ๊ตฌ์ฑ์ ํตํ ๊ด๋ฆฌ ๋ฐฉ์ ๋ ํผ๋ฐ์ค 4
- ํ๊ฒฝ ๋ณ์ ์ค์ ๋ฐ Secret ๊ตฌ์ฑ์ ํตํ ๊ด๋ฆฌ ๋ฐฉ์ ๋ ํผ๋ฐ์ค 5
๊ธฐ์กด์ ๋ค๋ฃจ๋ News ๊ตฌ์กฐ์ฒด์ CoreData์์ ์ฐ๋ ScrapedNews ๊ตฌ์กฐ์ฒด์ ํํ๋ ๋์ผํ๋ ๋ค๋ฅด๊ฒ ์ธ ์ ๋ฐ์ ์์๋ค. ์ธ๋ถ์์ ๊ฐ์ ธ์ค๋ ์ํฐํฐ์ ํํ์ ๋์ผ ํ๋๋ผ๋ ๋ด๋ถ์ Database์ ์ฐ์ธ๋ค๋ ์๋ฏธ๋ ์๋ฌด๋๋ ๋ถ๋ณ์ฑ์ ๊ทผ๊ฑฐํ์ฌ ์ด๋ฅผ ์์์ ์ผ๋ก ์์ ํ๋ ๊ฑด ์ ํํ๋ ๊ฒ ๊ฐ์๋ค. ๋ฐ๋ผ์, ์๋ก ๋ค๋ฅธ ๋ค์ด๋ฐ์ผ๋ก ๊ด๋ฆฌํ๊ณ ํ์์ ๋ฐ๋ผ์ DB์์ ๊ฐ์ ธ์จ ๋ฐ์ดํฐ๋ฅผ ํ์ฑํ์ฌ ์ํฐํฐ์ ๋ฐ์ดํฐ ํํ๋ก ์ฌ์ฉํ๋๊ฒ ์คํ๋ ค ์์ ํ๋ค๊ณ ์๊ฐ๋์ด ์ฐ์ ์ ๋ถ๋ฆฌํ ์ฑ ์์ ์ ์งํํด๋์๋ค.
MockData๋ฅผ ๊ตฌ์ฑํจ์ ์์ด์ ๋ถ๋ฆฌ๊ฐ ์๋ค๋ณด๋ ๋ง์๋๋ก ์กฐ์๋ ๋ฐ์ดํฐ๋ฅผ ์ฃผ์ ํ๊ธฐ๊ฐ ๊น๋ค๋ก์ ๋ค. ๊ฐ๋จํ๊ฒ ์ ๋ฌ๋๋ ํจ์๋ง ๊ตฌํํ๋ ๊ฒ์ด ์๋ ์ค์ ๋ก DB ๋ด๋ถ์ ๋ฐ์ดํฐ๋ฅผ ์ฃผ์ ํ๊ณ ํ ์คํธํ๋ ๋ฐฉ์์ ๊ณ ๋ คํ๊ฒ ๋๋ฉด์ ๊ธฐ์กด์ ์๋ ๊ฐ๋ฒผ์ด ์์ค์ Test Mock์ ๋์ด๋ด๊ณ ๋ค์ ๊ตฌ์ฑ์ ์์ํ๋ค. ๊ตฌ์ฒด์ ์ผ๋ก CoreData๋ฅผ ๊ฒฝ์ ํ์ฌ News ๊ฐ์ ์ ๋ฌํ๊ณ ์ญ์ ํ๋ ๋ก์ง์ ์ถ๊ฐํ์ฌ ๊ตฌ์ฑํ์๋ค.
์ฐธ๊ณ ๋ ํผ๋ฐ์ค
์ฐธ๊ณ ๋ ํผ๋ฐ์ค ๋ ํฌ
Large ํ์ดํ์ ์ ์งํ๋ฉด์ ๋ค๋น๊ฒ์ด์ ๋ฒํผ์ ์์น๋ฅผ ์ ์ ํ ๊ณณ์ ์์น ์ํค๊ณ ์ถ์์ผ๋, ํ์คํ ์กฐ์ ํ๋ ๊ฒ ์๊ฐ๋ณด๋ค ์ด๋ ค์ ๋ค. ์ผ๋ฐ์ ์ธ UI button์ผ๋ก ๊ตฌ์ฑํ์ฌ bar์ ์ฌ๋ฆฌ๋ ๋ฐฉ์์ ์ถ์ฒํ์ฌ ์๋ช ์ฃผ๊ธฐ์ ๋ฐ๋ผ์ ํด๋น ๋ทฐ๋ฅผ ๋ณด์ฌ์ฃผ๋ ๋ฐฉ์์ผ๋ก ๊ตฌํํ๋ค. ์คํ ๋ ์ด์์์ด ์๊ฐ๋ณด๋ค ๊น๋ค๋กญ๊ณ ๋ฒํผ์ ์ฌ์ด์ฆ์ ๊ด๊ณํ์ฌ ๋ทฐ ์์ฒด๊ฐ ๋ง๊ฐ์ง๋ ๊ฒฝ์ฐ๋ ์๊ธฐ๋ฏ๋ก ์ฃผ์ํ๋ฉด์ ์ธํ ํด์ผํ ๊ฑฐ ๊ฐ์๋ค.
์ด๋ฒ ํ๋ก์ ํธ๋ฅผ ์งํํ๋ฉด์ ์ต๋ํ ๊ด๋ฆฌ๊ฐ ์ฉ์ดํ ๋ฐฉ๋ฒ์ผ๋ก ํ๋ ๊ธฐ์กด์ ์ฌ์ฉํ๋ MVP์ ํน์ฑ์ ์๊ณ ์ถ์ง ์์๋ค. ์ธ๋ถ์์ ๋ฐ์ดํฐ๋ฅผ ๋ค๋ฃจ๋ Repo์ ๊ฐ์ ์ญํ ์ Manager์๊ฒ ์ผ์ํ๊ณ ๋ ์ ์์ผ๋ฉด protocol๋ก ๋ถ๋ฆฌํ์ฌ ๊ทธ ์ญํ ์ ๊ตฌ๋ถ์ง์ด ๋์๋ค. ๋ํ ๋คํธ์ํฌ ๊ตฌ์ฑ์ ์์ด์๋ ํ ๊ณณ์ ๋ชฐ์๋๋ ๋ฐฉ์ ๋ณด๋ค๋ ๋ถ๋ฆฌ๋ฅผ ํตํด์ ์ ์ง ๋ณด์๊ฐ ์ํํ๋๋ก ๊ตฌ์ฑํ๋ ํธ์ด ์ข๊ธฐ์ ๋ถ๋ฆฌํ์๊ณ , ์ฐ๋ฉด์ Moya ๋ฑ์ ์๋ํํฐ๊ฐ ์ ์ง์ฌ์ก๋ค๊ณ ๋๊ผ๋ค. ํด๋ฆฐํ๊ฒ ์์ฑํ๋ค๋ ๊ฑด ๊ฐ์ฅ ์ ๋ฟ๋ ๊ฑด ์ญํ ์ ๋ถ๋ฆฌ์ ๋ฐ์ดํฐ ์ฃผ์ ๋ฑ ์๊ฐํด์ผํ ๋ถ๋ถ์ด ๋ง๋ค๋ ๊ฒ์ด๋ค. ๊ธฐ์กด์ ๋ญํฑ์ด๋ก ์์ ํ๋ ์ฝ๋๋ค๊ณผ๋ ๋ฌ๋ฆฌ ์ฃผ์ ๋ฐฉ์์ ์ฝ๋์ ์ธ๋ถ์ ๋ถ๋ฆฌ๋ฅผ ํตํ ํธ์ถ์ ํ์คํ ํ ์คํธ ํ๊ธฐ์๋ ๊ต์ฅํ ์ฉ์ดํ๋ค.
๋ํ ์๋ฟ์ ๊ฑด DI์ ๋ถ๋ฆฌ์ ํ์คํ ๋ ์ด์ด ๊ตฌ๋ถ์ด์๋ค. Data ๋ ์ด์ด, ์ํฐํฐ ๋ฑ ์๋ก๊ฐ ์ ๋ ์์์ ์๋๋ ๊ฒฝ์ฐ์ ๋ํด์๋ ํ์คํ๊ฒ ๊ตฌ๋ถํ๊ณ ํ์ฌ ๊ตฌ์ฑํ Presenter, View์ ๊ด๊ณ์ ์์ด์๋ ์ฐธ์กฐ ๊ด๊ณ์ ๋ํด์๋ ์ญํ ์ ๋ช ํํ ํ๋๊ฒ ์ค์ํ๋ค. ๊ทธ๋ฆฌ๊ณ ๊ตฌ์ฑํ๋ฉด์ ๋๋ ๊ฑด๋ฐ View์์ ํ๋ฉด ์ ํ ๋ก์ง์ ์์ด์ ์์กด์ฑ ์ฃผ์ ์ด ์๋๋ฐ ํด๋น ๋ถ๋ถ์ Cordinator, Container ๋ฑ์ผ๋ก ๋ถ๋ฆฌํ์ฌ ์ง์ ์ ์ผ๋ก View์์ ๋ชจ๋ธ์ ์ธ์ ์ ํ๋ ๊ฑด ๋ง์ง ์์ ๋ณด์๋ค.
7. ์น ๋ทฐ์์ ์ ๋ฌ๋ ์ธํฐ๋ ์ ์ ๊ฒฐ๊ณผ๋ก ๋ค์ดํฐ๋ธ์ ์ธํฐ๋ ์ ์ด ๋์ํ์ง ์๋ ๋ฌธ์
swift์ ๋๋ถ๋ถ์ UI ์์์๋ UIResponder๊ฐ ์๋ค. ์ฒ๋ฆฌ๋์ง ์์ ์ด๋ฒคํธ๋ ๋ฆฌ์คํฐ๋ ์ฒด์ธ์ ํตํด ๋ฉํ๋ view๋ก ์ ๋ฌ๋๊ฒ ๋๋๋ฐ, ๋ด ์๊ฐ์ WKWebView๋ ์ฐฝ์ด ํ์ฑํ๋๋ฉด ๋ชจ๋ ํฐ์น ์ด๋ฒคํธ๋ฅผ ํก์ํ๋ค. ์๋ฌด๋ฆฌ ํฐ์นํด๋ ํ๋ฆฐํธ๊ฐ ๋์ ํ ๋์ํ์ง ์์ ๊ณ ๋ฏผํ๋ค๊ฐ webView ๋ด์ ์ธํฐ๋ ์ ์ ์๋ํ ํ์์ผ ์ ์์ ์ผ๋ก ์ด๋ฒคํธ ์ ๋ฌ์ด ๋ฐ์ํ๋ค. ์ด ํ ๋ฆฌ์คํฐ๋๋ฅผ ๊ฐ๋จํ๊ฒ ์์ฑํ๋ฏ๋ก์จ ํด๊ฒฐ!