Giter Club home page Giter Club logo

ios-box-office's Introduction

๐ŸŽฅ ๋ฐ•์Šค์˜คํ”ผ์Šค ๐Ÿฟ

๐Ÿ“– ๋ชฉ์ฐจ

  1. ๐Ÿ“ข ์†Œ๊ฐœ
  2. ๐Ÿ‘ค ํŒ€์›
  3. โฑ๏ธ ํƒ€์ž„๋ผ์ธ
  4. ๐Ÿ“Š UML & ํŒŒ์ผํŠธ๋ฆฌ
  5. ๐Ÿ“ฑ ์‹คํ–‰ ํ™”๋ฉด
  6. ๐Ÿงจ ํŠธ๋Ÿฌ๋ธ” ์ŠˆํŒ…
  7. ๐Ÿ‘ฌ ํŒ€ ํšŒ๊ณ 
  8. ๐Ÿ”— ์ฐธ๊ณ  ๋งํฌ

1. ๐Ÿ“ข ์†Œ๊ฐœ

์ผ๋ณ„ ๋ฐ•์Šค์˜คํ”ผ์Šค API๋ฅผ ์ด์šฉํ•ด์„œ ๋ฐ•์Šค์˜คํ”ผ์Šค ์ •๋ณด๋ฅผ ๋ฐ›์•„์˜ค๊ณ  ์›ํ•˜๋Š” ์˜ํ™”์˜ ์ •๋ณด๋ฅผ ๋ณด์—ฌ๋“œ๋ฆฝ๋‹ˆ๋‹ค!

ํ•ต์‹ฌ ๊ฐœ๋… ๋ฐ ๊ฒฝํ—˜

  • Networking
    • URLSession์„ ์ด์šฉํ•œ ๋„คํŠธ์›Œํ‚น
  • TestDouble
    • TestDouble ๊ฐ์ฒด๋ฅผ ์ด์šฉํ•˜์—ฌ ๋„คํŠธ์›Œํฌ๊ฐ€ ์—ฐ๊ฒฐ๋˜์–ด ์žˆ์ง€ ์•Š์€ ๊ฒฝ์šฐ์—๋„ ํ…Œ์ŠคํŠธ
  • DTO
    • JSON ๋ฐ์ดํ„ฐ๋ฅผ ๋””์ฝ”๋”ฉํ•  DTO ๊ฐ์ฒด ๊ตฌํ˜„
      • ์‚ฌ์šฉํ•  ๋ฐ์ดํ„ฐ๋งŒ์œผ๋กœ ์ด๋ฃจ์–ด์ง„ Model ๊ฐ์ฒด ๊ตฌํ˜„
  • CollectionView
    • CollectionView๋ฅผ ํ™œ์šฉํ•œ UI๊ตฌํ˜„
    • CompositionalLayout๋ฅผ ํ™œ์šฉํ•œ Layout ์„ค์ •
    • DiffableDataSource๋ฅผ ํ™œ์šฉํ•œ UI ์—…๋ฐ์ดํŠธ
    • Grid ๋ชจ๋“œ ๊ตฌํ˜„
  • MVC
    • MVCํŒจํ„ด์„ ํ™œ์šฉํ•˜์—ฌ ๊ฐ์ฒด๋ฅผ ์—ญํ• ์— ์•Œ๋งž๊ฒŒ ๋ถ„๋ฆฌํ•˜์—ฌ ํ”„๋กœ์ ํŠธ ๊ตฌํ˜„
  • NSCache
    • NSCache๋ฅผ ํ™œ์šฉํ•˜์—ฌ ๋ฐ›์•„์˜จ ์ด๋ฏธ์ง€๋ฅผ ์žฌ์‚ฌ์šฉ
  • UICalendarView
    • UICalendarView๋ฅผ ํ™œ์šฉํ•˜์—ฌ ๋‹ฌ๋ ฅ ๊ตฌํ˜„
  • UIAlertController
    • actionSheet ํ™œ์šฉ

2. ๐Ÿ‘ค ํŒ€์›

kyungmin ๐Ÿผ Erick ๐Ÿถ

3. โฑ๏ธ ํƒ€์ž„๋ผ์ธ

ํ”„๋กœ์ ํŠธ ๊ธฐ๊ฐ„ : 2023.07.24 ~ 2023.08.18

๋‚ ์งœ ๋‚ด์šฉ
2023.07.24 โ–ซ๏ธ JSON ๋ฐ์ดํ„ฐ๋ฅผ ๋””์ฝ”๋”ฉํ•  Entity ๊ฐ์ฒด ๊ตฌํ˜„
2023.07.25 โ–ซ๏ธ ์ฝ”๋“œ ๊ฐœ์„ ์„ ์œ„ํ•œ ๋ฆฌํŽ™ํ† ๋ง
2023.07.26 โ–ซ๏ธ ๋„คํŠธ์›Œํ‚น ์ž‘์—…์„ ํ•  NetworkManager ๊ตฌํ˜„
2023.07.27 โ–ซ๏ธ TestDouble์„ ์ด์šฉํ•œ ํ…Œ์ŠคํŠธ ์ƒ์„ฑ
2023.07.28 โ–ซ๏ธ ์ฝ”๋“œ ๊ฐœ์„ ์„ ์œ„ํ•œ ๋ฆฌํŽ™ํ† ๋ง
โ–ซ๏ธ README ์ž‘์„ฑ
2023.07.31 โ–ซ๏ธ CollectionViewCell ๊ตฌํ˜„
2023.08.01 โ–ซ๏ธ CollectionView๋กœ UI ๊ตฌํ˜„
โ–ซ๏ธ BoxOffice์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์™€ ๊ด€๋ฆฌํ•˜๋Š” BoxOfficeManager๊ตฌํ˜„
โ–ซ๏ธ ์‹ค์ œ ์‚ฌ์šฉํ•  ๋ฐ์ดํ„ฐ๋งŒ์œผ๋กœ ์ด๋ฃจ์–ด์ง„ Entity ๊ฐ์ฒด DailyBoxOffice ๊ตฌํ˜„
โ–ซ๏ธ refreshControl์„ ์ด์šฉํ•œ ๋ฐ์ดํ„ฐ ๋ฆฌ๋กœ๋“œ ๋กœ์ง ๊ตฌํ˜„
2023.08.02 โ–ซ๏ธ ๋ฐ์ดํ„ฐ ๋กœ๋“œ์— ์‹คํŒจํ–ˆ์„ ๋•Œ Alert๊ฐ€ ๋œจ๋„๋ก ๊ตฌํ˜„
2023.08.03 โ–ซ๏ธ ์ฝ”๋“œ ๊ฐœ์„ ์„ ์œ„ํ•œ ๋ฆฌํŽ™ํ† ๋ง
2023.08.04 โ–ซ๏ธ ์ฝ”๋“œ ๊ฐœ์„ ์„ ์œ„ํ•œ ๋ฆฌํŽ™ํ† ๋ง
โ–ซ๏ธ README ์ž‘์„ฑ
2023.08.05 โ–ซ๏ธ MoiveDetailView ๋ฐ Controller ๊ตฌํ˜„
โ–ซ๏ธBoxOfficeManager์— fetchPosterImage์ถ”๊ฐ€
2023.08.08 โ–ซ๏ธ ์ด๋ฏธ์ง€ ์บ์‹ฑ์„ ์œ„ํ•œ CacheManager ๊ตฌํ˜„
โ–ซ๏ธ CalendarView๋ฐ Controller ๊ตฌํ˜„
2023.08.09 โ–ซ๏ธ ์ฝ”๋“œ ๊ฐœ์„ ์„ ์œ„ํ•œ ๋ฆฌํŽ™ํ† ๋ง
2023.08.11 โ–ซ๏ธ ์ฝ”๋“œ ๊ฐœ์„ ์„ ์œ„ํ•œ ๋ฆฌํŽ™ํ† ๋ง
โ–ซ๏ธ README ์ž‘์„ฑ
2023.08.15 โ–ซ๏ธ CollectionView์˜ listMode์™€ gridMode ๊ตฌํ˜„
โ–ซ๏ธ actionSheet์œผ๋กœ ์‚ฌ์šฉ์ž๊ฐ€ listMode์™€ gridMode๋ฅผ ์„ ํƒํ•  ์ˆ˜ ์žˆ๋„๋ก ๊ตฌํ˜„
โ–ซ๏ธ listMode ๋ฐ DetailView์˜ ๋‹ค์ด๋‚˜๋ฏน ํƒ€์ž… ๊ตฌํ˜„
2023.08.17 โ–ซ๏ธ ์ฝ”๋“œ ๊ฐœ์„ ์„ ์œ„ํ•œ ๋ฆฌํŽ™ํ† ๋ง
2023.08.18 โ–ซ๏ธ README ์ž‘์„ฑ

4. ๐Ÿ“Š UML & ํŒŒ์ผํŠธ๋ฆฌ

UML


ํŒŒ์ผํŠธ๋ฆฌ

BoxOffice
โ”œโ”€โ”€ App
โ”‚ย ย  โ”œโ”€โ”€ AppDelegate.swift
โ”‚ย ย  โ””โ”€โ”€ SceneDelegate.swift
โ”œโ”€โ”€ Controller
โ”‚ย ย  โ”œโ”€โ”€ BoxOfficeViewController.swift
โ”‚ย ย  โ”œโ”€โ”€ CalendarViewController.swift
โ”‚ย ย  โ””โ”€โ”€ MovieDetailViewController.swift
โ”œโ”€โ”€ Model
โ”‚ย ย  โ”œโ”€โ”€ Manager
โ”‚ย ย  โ”‚ย ย  โ”œโ”€โ”€ BoxOfficeManager.swift
โ”‚ย ย  โ”‚ย ย  โ”œโ”€โ”€ CacheManager.swift
โ”‚ย ย  โ”‚ย ย  โ””โ”€โ”€ DataManager.swift
โ”‚ย ย  โ”œโ”€โ”€ DTO
โ”‚ย ย  โ”‚ย ย  โ”œโ”€โ”€ BoxOffice.swift
โ”‚ย ย  โ”‚ย ย  โ”œโ”€โ”€ Movie.swift
โ”‚ย ย  โ”‚ย ย  โ””โ”€โ”€ PosterImageInformation.swift
โ”‚ย ย  โ”œโ”€โ”€ DailyBoxOffice.swift
โ”‚ย ย  โ”œโ”€โ”€ MovieInformation.swift
โ”‚ย ย  โ””โ”€โ”€ TestDouble
โ”‚ย ย      โ”œโ”€โ”€ StubURLSession.swift
โ”‚ย ย      โ””โ”€โ”€ URLSessionProtocol.swift
โ”œโ”€โ”€ View
โ”‚   โ”œโ”€โ”€ BoxOfficeCollectionViewGridCell.swift
โ”‚   โ”œโ”€โ”€ BoxOfficeCollectionViewListCell.swift
โ”‚   โ””โ”€โ”€ MovieDetailScrollView.swift
โ”œโ”€โ”€ Extension
โ”‚ย ย  โ”œโ”€โ”€ Date+.swift
โ”‚ย ย  โ”œโ”€โ”€ DateFormatter+.swift
โ”‚ย ย  โ”œโ”€โ”€ NumberFormatter+.swift
โ”‚ย ย  โ”œโ”€โ”€ UIAlertController+.swift
โ”‚ย ย  โ”œโ”€โ”€ UILabel+.swift
โ”‚ย ย  โ”œโ”€โ”€ URL+.swift
โ”‚ย ย  โ”œโ”€โ”€ URLRequest+.swift
โ”‚ย ย  โ””โ”€โ”€ URLSession+.swift
โ”œโ”€โ”€ Error
โ”‚ย ย  โ”œโ”€โ”€ DataError.swift
โ”‚ย ย  โ””โ”€โ”€ NetworkError.swift
โ”œโ”€โ”€ NetWork
โ”‚ย ย  โ””โ”€โ”€ NetworkManager.swift
โ””โ”€โ”€ Util
 ย ย  โ””โ”€โ”€ Path.swift

5. ๐Ÿ“ฑ ์‹คํ–‰ ํ™”๋ฉด

๋ฐ•์Šค์˜คํ”ผ์Šค ๋ฐ์ดํ„ฐ ๋กœ๋“œ ํ™”๋ฉด์„ ๋‹น๊ฒจ ๋ฐ์ดํ„ฐ ๋ฆฌ๋กœ๋“œ
๋ฐ์ดํ„ฐ ๋กœ๋“œ์— ์‹คํŒจํ–ˆ์„ ๋•Œ ์•Œ๋ฆผํ™”๋ฉด ์˜ํ™” ์ƒ์„ธํ™”๋ฉด
๋‚ ์งœ์„ ํƒ ํ™”๋ฉด ๋ชจ๋“œ ๋ณ€๊ฒฝ

6. ๐Ÿงจ ํŠธ๋Ÿฌ๋ธ” ์ŠˆํŒ…

1๏ธโƒฃ ๊ฐ์ฒด ๋ถ„๋ฆฌ

๐Ÿ”ฅ ๋ฌธ์ œ์ 

Controller๋‚˜ ํ•˜๋‚˜์˜ Model์—์„œ ๋งŽ์€ ๋กœ์ง์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ๋ณด๋‹จ ์ž์‹ ์˜ ์—ญํ• ์„ ๊ฐ€์ง„ ๊ฐ์ฒด๋“ค์„ ์ž˜ ๊ตฌํ˜„ํ•˜์—ฌ ํ•„์š”ํ• ๋•Œ๋งŒ ๋ถˆ๋Ÿฌ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด ๊ฐ์ฒด๊ฐ„์˜ ์˜์กด์„ฑ๋„ ๋‚ฎ์ถ”๊ณ  ์ฝ”๋“œ๋ฅผ ์žฌ์‚ฌ์šฉํ•˜๋Š” ์ธก๋ฉด์— ์ข‹์„ ๊ฒƒ ๊ฐ™์•„ ๊ฐ์ฒด ๋ถ„๋ฆฌ์— ๋Œ€ํ•œ ๊ณ ๋ฏผ์„ ๋งŽ์ด ํ–ˆ์Šต๋‹ˆ๋‹ค.

๋งŽ์€ ๊ธฐ๋Šฅ๋“ค์„ ๊ตฌํ˜„ํ•˜๋‹ค ๋ณด๋‹ˆ Model์ชฝ์—์„œ ๊ธฐ๋Šฅ ๋ถ„๋ฆฌ๋ฅผ ๋งŽ์ด ํ–ˆ์Œ์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ  VC์˜ ์ฝ”๋“œ๊ฐ€ ๊ธธ์–ด์ ธ ๊ฐ€๋…์„ฑ์ด ๋–จ์–ด์ง€๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

๐Ÿงฏ ํ•ด๊ฒฐ๋ฐฉ๋ฒ•

1. Model์˜ ๊ธฐ๋Šฅ ๋ถ„๋ฆฌ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ž์‹ ์˜ ์—ญํ• ์„ ๊ฐ€์ง„ ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค์–ด ํ”„๋กœ์ ํŠธ๋ฅผ ๊ตฌํ˜„ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

  • DTO : ์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ ๋ฐ›์€ Data๋ฅผ Decodeํ• ๋•Œ ์“ฐ๋Š” ํƒ€์ž…
  • Model Data : ์‹ค์ œ App์—์„œ ์‚ฌ์šฉํ•  Data ํƒ€์ž…
  • DataManager : DTO๋ฅผ Model Data๋กœ ๋ณ€ํ™˜ํ•ด์ฃผ๋Š” ์—ญํ™œ
  • NetworkManager : ์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ Data๋ฅผ ๋ฐ›์•„์˜ค๋Š” ์—ญํ• 
  • BoxOfficeManager : Controller์—์„œ ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„ ๊ด€๋ฆฌํ•˜๋Š” ํƒ€์ž…
  • Extension
    • URLSession : ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•ด URLSeesionProtocol์„ ์ฑ„ํƒํ•˜๊ธฐ ์œ„ํ•œ extension
    • URLRequest : Header๊ฐ’์„ ์ง€์ •ํ•œ URLRequest๋ฅผ ๋ฐ˜ํ™˜ํ•ด์ฃผ๋Š” kakaoURLRequest ๋ฉ”์„œ๋“œ ์ถ”๊ฐ€
    • URL : URL์„ ์ƒ์„ฑํ•˜๋Š” kobisURL, kakaoURL ๋ฉ”์„œ๋“œ ์ถ”๊ฐ€
    • UILabel : attributedText์˜ ๋ถ€๋ถ„ ์ƒ‰์„ ๋ณ€๊ฒฝํ•ด์ฃผ๋Š” convertColor๋ฉ”์„œ๋“œ ์ถ”๊ฐ€
    • UIAlertController : ์ปค์Šคํ…€ํ•œ UIAlertController์„ ๋ฐ˜ํ™˜ํ•˜๋Š” customAlert ๋ฉ”์„œ๋“œ ์ถ”๊ฐ€
    • Date : Date๋ฅผ ๋ฐ˜ํ™˜ํ•ด์ฃผ๋Š” date๋ฉ”์„œ๋“œ ์ถ”๊ฐ€
    • DateFormatter : ์˜ค๋Š˜๋กœ๋ถ€ํ„ฐ ์›ํ•˜๋Š” ๋งŒํผ ๋–จ์–ด์ง„ ๋‚ ์งœ๋ฅผ ์›ํ•˜๋Š” ํฌ๋ฉง์œผ๋กœ ๋ฐ˜ํ™˜ํ•˜๋Š” dateString ๋ฉ”์„œ๋“œ ์ถ”๊ฐ€
    • NumberFormatter : ์ˆซ์ž๋กœ ์ด๋ฃจ์–ด์ง„ String์„ ๋ฐ›์•„ Decimal์Šคํƒ€์ผ๋กœ ํฌ๋ฉงํ•ด์ฃผ๋Š” decimalString ๋ฉ”์„œ๋“œ ์ถ”๊ฐ€

2. VC ๊ธฐ๋Šฅ ๋ถ„๋ฆฌ extension๊ณผ MARK๋ฅผ ์ด์šฉํ•ด ๊ธฐ๋Šฅ ๋ถ„๋ฆฌ๋ฅผ ํ•ด์„œ ์ฝ”๋“œ์˜ ๊ฐ€๋…์„ฑ์„ ๋†’์˜€์Šต๋‹ˆ๋‹ค.

๐Ÿ”บextension ํ•˜์ง€ ์•Š์€ ๋ถ€๋ถ„

  • stored property
  • initializer
  • viewDidLoad

๐Ÿ”บextension-setupComponents

  • component๊ด€๋ จ ์„ธํŒ…

๐Ÿ”บextension-configureUI

  • ๊ฐ๊ฐ์˜ UI๊ฐ์ฒด์˜ addSubview

๐Ÿ”บextension-setupConstraint

  • ๊ฐ๊ฐ์˜ UI๊ฐ์ฒด์˜ constraint

๐Ÿ”บextension-buttonAction

  • ๋ฒ„ํŠผ ๋ฉ”์„œ๋“œ

๐Ÿ”บ๊ทธ ์™ธ ๊ธฐ๋Šฅ

  • ๋ธ๋ฆฌ๊ฒŒ์ดํŠธ, ๋ฐ์ดํ„ฐ์†Œ์Šค ๋“ฑ..

2๏ธโƒฃ Test Double

๐Ÿ”ฅ ๋ฌธ์ œ์ 

๋„คํŠธ์›Œํฌ๊ฐ€ ์—ฐ๊ฒฐ๋˜์–ด ์žˆ์ง€ ์•Š์€ ์ƒํ™ฉ์—์„œ๋„ NetworkManager์˜ startLoad ๋กœ์ง์ด ์ž˜ ์ž‘๋™ํ•˜๋Š”์ง€ ํ…Œ์ŠคํŠธ๋ฅผ ํ•˜๊ณ  ์‹ถ์—ˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ URLSession์˜ dataTask ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ๋„คํŠธ์›Œํฌ๊ฐ€ ์—ฐ๊ฒฐ๋˜์–ด ์žˆ์ง€ ์•Š์€ ์ƒํ™ฉ์—์„œ๋Š” ํ…Œ์ŠคํŠธ๊ฐ€ ์–ด๋ ค์› ์Šต๋‹ˆ๋‹ค.

๐Ÿงฏ ํ•ด๊ฒฐ๋ฐฉ๋ฒ•

ํ…Œ์ŠคํŠธ๊ฐ€ ์–ด๋ ค์šด ๊ฒฝ์šฐ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” Test Double ๊ฐ์ฒด StubURLSession์„ ๊ตฌํ˜„ํ•˜์—ฌ ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ํ•˜์˜€์Šต๋‹ˆ๋‹ค. StubURLSession๋Š” ์‹ค์ œ dataTask์˜ ์—ญํ• ์„ ์ˆ˜ํ–‰ํ•˜์ง„ ์•Š์ง€๋งŒ ๊ป๋ฐ๊ธฐ dataTask๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์–ด ๋„ฃ์–ด์ค€ Dummy Data๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ฐ์ฒด์ž…๋‹ˆ๋‹ค.

๐Ÿ’‰ ์˜์กด์„ฑ ์ฃผ์ž…

protocol URLSessionProtocol {
    func dataTask(with urlRequest: URLRequest,  completionHandler: @escaping DataTaskCompletionHandler) -> URLSessionDataTask
}
struct NetworkManager {
    private let urlSession: URLSessionProtocol
    
    // ...
}
  • URLSessionProtocol์„ ๋งŒ๋“ค์–ด NetworkManager๊ฐ€ ํ”„๋กœํผํ‹ฐ๋กœ ํ•ด๋‹น ํƒ€์ž…์„ ๊ฐ€์ง€๊ณ  ์žˆ๋„๋ก ํ•˜์—ฌ URLSession๋ฟ๋งŒ ์•„๋‹ˆ๋ผ StubURLSession์„ ์ฃผ์ž…ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•˜์—ฌ ํ…Œ์ŠคํŠธํ–ˆ์Šต๋‹ˆ๋‹ค.

โœ… ํ…Œ์ŠคํŠธ

// given
// ๋ฐ›์•„์˜ฌ ๋ฐ์ดํ„ฐ๋ฅผ ์ž„์˜๋กœ ์ƒ์„ฑ
let dummy = DummyData(data: data, response: response, error: nil)
let stubUrlSession = StubURLSession(dummy: dummy)
sut = NetworkManager(urlSession: stubURLSession)

// when
sut?.requestData(from: url, completion: { result in
    // then
    // startLoad๋กœ ๋ฐ›์•„์˜จ ๋ฐ์ดํ„ฐ๋Š” DummyData์˜ ๋ฐ์ดํ„ฐ
    XCTAssertEqual(data, dataAsset.data)
    expectation.fulfill()
})
  • StubURLSession๋Š” DummyData๋ฅผ ๊ฐ€์ง€๊ณ  dataTask ๋ฉ”์„œ๋“œ๋ฅผ ์‹คํ–‰์‹œ StubURLSessionDataTask์„ ์ด์šฉํ•ด์„œ DummyData๋งŒ ๋˜์ ธ์ฃผ๋„๋ก ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค.

3๏ธโƒฃ Closure Capture

๐Ÿ”ฅ ๋ฌธ์ œ์ 

BoxOfficeManager์˜ fetchBoxOffice์—์„œ requestData๋กœ ๋„คํŠธ์›Œํ‚น์„ํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์˜ค๋Š” ๋กœ์ง์— ํด๋กœ์ ธ ๋‚ด์—์„œ BoxOfficeManager์˜ ํ”„๋กœํผํ‹ฐ์ธ dailyBoxOffices๋กœ ์ ‘๊ทผํ•˜์—ฌ ํ”„๋กœํผํ‹ฐ๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” ๋กœ์ง์ด ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ์ด๋•Œ ํด๋กœ์ ธ๊ฐ€ self๋ฅผ ์บก์ณํ•˜๋ฉด์„œ RC๊ฐ€ ์˜ฌ๋ผ๊ฐ€ ๋ฉ”๋ชจ๋ฆฌ ํ•ด์ œ๊ฐ€ ์•ˆ๋˜๋Š” ๊ฐ•ํ•œ์ฐธ์กฐ์ˆœํ™˜์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

final class BoxOfficeManager {
    private(set) var dailyBoxOffices: [DailyBoxOffice] = []
    
    // ...
    func fetchBoxOffice(completion: @escaping (Bool) -> Void) {
        
        // ...
        networkManager.requestData(from: url) { result in
            switch result {
            case .success(let data):
                do {
                    let boxOffice = try JSONDecoder().decode(BoxOffice.self, from: data)
                    self.dailyBoxOffices = DataManager.boxOfficeTransferDailyBoxOfficeData(boxOffice: boxOffice)
                    completion(true)
                } // ...
            }
        }
    }
}

๐Ÿงฏ ํ•ด๊ฒฐ๋ฐฉ๋ฒ•

ํด๋กœ์ ธ์˜ ์บก์ณ๋กœ self์˜ RC๊ฐ€ ์˜ฌ๋ผ๊ฐ€๋Š” ๊ฒƒ์„ ๋ง‰์•„์ฃผ๊ธฐ ์œ„ํ•ด [weak self]๋กœ ํด๋กœ์ ธ๋ฅผ ์บก์ณํ•˜์—ฌ ๊ฐ•ํ•œ์ˆœํ™˜์ฐธ์กฐ๋ฅผ ์˜ˆ๋ฐฉํ•˜์˜€์Šต๋‹ˆ๋‹ค.

final class BoxOfficeManager {
    private(set) var dailyBoxOffices: [DailyBoxOffice] = []
    
    // ...
    func fetchBoxOffice(completion: @escaping (Bool) -> Void) {
        
        // ...
        networkManager.requestData(from: url) { [weak self] result in
            guard let self else {
                return
            }
                                               
            switch result {
            case .success(let data):
                do {
                    let boxOffice = try JSONDecoder().decode(BoxOffice.self, from: data)
                    self.dailyBoxOffices = DataManager.boxOfficeTransferDailyBoxOfficeData(boxOffice: boxOffice)
                    completion(true)
                } // ...
            }
        }
    }
}

4๏ธโƒฃ endRefreshing

๐Ÿ”ฅ ๋ฌธ์ œ์ 

ํ™”๋ฉด์„ ์œ„๋กœ ๋“œ๋ ˆ๊ทธํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฆฌ๋กœ๋“œํ•  ๋•Œ ๋ฐ์ดํ„ฐ ๋กœ๋“œ์— ์‹คํŒจํ•˜๋ฉด alert์ด ๋œจ๋Š”๋ฐ ์ด๋•Œ refreshControl?.endRefreshing()์ด ์•ˆ๋˜๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

๐Ÿงฏ ํ•ด๊ฒฐ๋ฐฉ๋ฒ•

endRefreshing์˜ ์• ๋‹ˆ๋ฉ”์ด์…˜ ํšจ๊ณผ์™€ present์˜ ์• ๋‹ˆ๋ฉ”์ด์…˜ ํšจ๊ณผ๋ฅผ ๋™์‹œ์— ์ฒ˜๋ฆฌํ•˜์ง€ ๋ชปํ•˜์—ฌ ๋ฐœ์ƒํ•˜๋Š” ์—๋Ÿฌ์˜€์Šต๋‹ˆ๋‹ค. present์˜ animated๋ฅผ false๋กœ ์„ค์ •ํ•˜์—ฌ ํ•ด๊ฒฐํ•˜์˜€์Šต๋‹ˆ๋‹ค.

present(alert, animated: false)


5๏ธโƒฃ Hashable

๐Ÿ”ฅ ๋ฌธ์ œ์ 

๊ธฐ์กด์˜ CollectionView์— CompositionalLayout์™€ DiffableDataSource๋ฅผ ์ ์šฉํ•˜๋Š” ๊ณผ์ •์—์„œ DailyBoxOffice ๊ฐ์ฒด๊ฐ€ Hashable์„ ์ฑ„ํƒํ•ด์•ผ ํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  Hashalbe์„ ์ฑ„ํƒํ•  ๋•Œ struct์˜ ๋ชจ๋“  ํ”„๋กœํผํ‹ฐ๋Š” Hashable์„ ์ค€์ˆ˜ํ•ด์•ผ ํ•œ๋‹ค๊ณ  ๋‚˜์™€์žˆ์—ˆ์ง€๋งŒ rankStateColor๋Š” typealias ํƒ€์ž…์ด๋ผ Hashable์„ ์ค€์ˆ˜ํ•˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.

For a struct, all its stored properties must conform to Hashable.

๐Ÿงฏ ํ•ด๊ฒฐ๋ฐฉ๋ฒ•

rankStateColor๋Š” rankState property๋ฅผ ํ†ตํ•ด์„œ ๋งŒ๋“ค์–ด์ง€๋Š” property์ด๊ธฐ ๋•Œ๋ฌธ์— hash value๋ฅผ ๋น„๊ตํ• ๋•Œ ํ•„์š”ํ•˜์ง€ ์•Š๋Š” ๊ฐ’์ด๋ผ๊ณ  ์ƒ๊ฐํ•ด์„œ hashํ•จ์ˆ˜์—์„œ ๋นผ์คฌ์Šต๋‹ˆ๋‹ค.

typealias RankStateColor = (targetString: String, color: UIColor)

struct DailyBoxOffice: Hashable {
    let movieCode: String
    let rank: String
    let rankState: String
    let movieTitle: String
    let dailyAndTotalAudience: String
    let rankStateColor: RankStateColor
    
    static func == (lhs: DailyBoxOffice, rhs: DailyBoxOffice) -> Bool {
        return lhs.movieCode == rhs.movieCode
            && lhs.rank == rhs.rank
            && lhs.rankState == rhs.rankState
            && lhs.movieTitle == rhs.movieTitle
            && lhs.dailyAndTotalAudience == rhs.dailyAndTotalAudience
    }
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(movieCode)
        hasher.combine(rank)
        hasher.combine(rankState)
        hasher.combine(movieTitle)
        hasher.combine(dailyAndTotalAudience)
    }
}

6๏ธโƒฃ DelegateํŒจํ„ด ์ˆœํ™˜์ฐธ์กฐ

๐Ÿ”ฅ ๋ฌธ์ œ์ 

Delegate ํŒจํ„ด์„ ์‚ฌ์šฉํ•  ๋•Œ ์ˆœํ™˜์ฐธ์กฐ๊ฐ€ ์ผ์–ด๋‚˜ ๋ฉ”๋ชจ๋ฆฌ ํ•ด์ œ๊ฐ€ ๋˜์ง€ ์•Š์„ ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

์˜ˆ์‹œ์ฝ”๋“œ

protocol ModelDelegate: AnyObject {}

class Model {
    var delegate: ModelDelegate?
        deinit {
        print("deinit Model")
    }
}

class ViewController: ModelDelegate {
    var model: Model?
    
    deinit {
        print("deinit ViewController")
    }
    
    func start() {
 
        model = Model()
        model?.delegate = self
    }
}

var viewController: ViewController? = ViewController()
viewController?.start()
viewController = nil
  • model?.delegate = self๋กœ delegate ํ”„๋กœํผํ‹ฐ๊ฐ€ ViewController๋ฅผ ์ฐธ์กฐํ•˜๊ณ , ViewController์—์„œ Model์„ ํ”„๋กœํผํ‹ฐ๋กœ ๊ฐ€์ง€๊ณ  ์žˆ์–ด ์ฐธ์กฐํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.
  • ๋”ฐ๋ผ์„œ ViewController = nil์„ ํ•˜์—ฌ ViewController๋ฅผ ํ•ด์ œ ์‹œํ‚ค๋ ค๊ณ  ํ•ด๋„ ๊ฐ•ํ•œ์ˆœํ™˜์ฐธ์กฐ๊ฐ€ ์ผ์–ด๋‚˜ ๋ฉ”๋ชจ๋ฆฌ ํ•ด์ œ๊ฐ€ ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

๐Ÿงฏ ํ•ด๊ฒฐ๋ฐฉ๋ฒ•

์ˆœํ™˜์ฐธ์กฐ๊ฐ€ ์ผ์–ด๋‚˜๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•ด์ฃผ๊ธฐ ์œ„ํ•ด์„œ delegate ํ”„๋กœํผํ‹ฐ๋ฅผ weak var๋กœ ์„ ์–ธํ•˜๋„๋ก ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

์˜ˆ์‹œ ์ฝ”๋“œ

protocol ModelDelegate: AnyObject {}

class Model {
    weak var delegate: ModelDelegate?
        deinit {
        print("deinit Model")
    }
}

class ViewController: ModelDelegate {
    var model: Model?
    
    deinit {
        print("deinit ViewController")
    }
    
    func start() {
 
        model = Model()
        model?.delegate = self
    }
}

var viewController: ViewController? = ViewController()
viewController?.start()
viewController = nil

// deinit ViewController
// deinit Model
  • delegate ํ”„๋กœํผํ‹ฐ๋ฅผ weak var๋กœ ์•ฝํ•œ ์ฐธ์กฐํ•˜์—ฌ viewController = nil์„ ํ•˜์—ฌ๋„ ๊ฐ•ํ•œ ์ˆœํ™˜ ์ฐธ์กฐ๊ฐ€ ์ผ์–ด๋‚˜์ง€ ์•Š๊ณ  ๋ฉ”๋ชจ๋ฆฌ ํ•ด์ œ๊ฐ€ ๋ฉ๋‹ˆ๋‹ค.

ํ”„๋กœ์ ํŠธ ์ฝ”๋“œ

final class CalendarViewController: UIViewController {
    weak var delegate: CalendarViewControllerDelegate?
    
    // ...
}

7๏ธโƒฃ ํ™”๋ฉด ๋ชจ๋“œ ๋ณ€๊ฒฝ ์• ๋‹ˆ๋ฉ”์ด์…˜

๐Ÿ”ฅ ๋ฌธ์ œ์ 

์ฒ˜์Œ์—๋Š” ๋ชจ๋“œ ๋ณ€๊ฒฝ์„ ํ•  ๋•Œ collectionView.reloadData()๋กœ ๋ชจ๋“  ์…€์„ ๋‹ค์‹œ ๋กœ๋“œํ•˜์˜€์Šต๋‹ˆ๋‹ค. ์ด๋Ÿด ๊ฒฝ์šฐ ์• ๋‹ˆ๋ฉ”์ด์…˜ ์—†์ด ํ™”๋ฉด์ด ๋ฐ”๋กœ ๋ฐ”๊ปด ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ์ €ํ•˜์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค๋Š” ๋‹จ์ ์ด ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

๐Ÿงฏ ํ•ด๊ฒฐ๋ฐฉ๋ฒ•

setCollectionViewLayout๊ณผ reloadSections์„ ์‚ฌ์šฉํ•˜์—ฌ layout์ด ๋ฐ”๋€Œ๋Š” ์ˆœ๊ฐ„๊ณผ section์ด ๋ฆฌ๋กœ๋“œ ๋˜๋Š” ์ˆœ๊ฐ„์— ์• ๋‹ˆ๋ฉ”์ด์…˜ ํšจ๊ณผ๋ฅผ ์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค.

layout์ด ์—…๋ฐ์ดํŠธ ๋  ๋•Œ ์• ๋‹ˆ๋ฉ”์ด์…˜

switch collectionViewMode {
case .list:
    collectionView.setCollectionViewLayout(listLayout(), animated: true)
case .grid:
    collectionView.setCollectionViewLayout(gridLayout(), animated: true)
}

reloadSections์™€ apply๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ section์ด ์—…๋ฐ์ดํŠธ ๋  ๋•Œ ์• ๋‹ˆ๋ฉ”์ด์…˜

var snapshot = NSDiffableDataSourceSnapshot<Section, DailyBoxOffice>()
snapshot.appendSections([.main])
snapshot.appendItems(boxOfficeManager.dailyBoxOffices, toSection: .main)
snapshot.reloadSections([.main])

dailyBoxOfficeDataSource.apply(snapshot, animatingDifferences: true)

8๏ธโƒฃ dynamic type๊ณผ label์˜ ๋„ˆ๋น„

๐Ÿ”ฅ ๋ฌธ์ œ์ 

DetailView์— dynamic type์„ ์ ์šฉํ–ˆ์„๋•Œ ๊ธ€์”จ๊ฐ€ ์ปค์งˆ ๊ฒฝ์šฐ ๊ฐ๋…, ์ œ์ž‘๋…„๋„, ๊ฐœ๋ด‰์ผ ๋“ฑ์˜ titleLabel์˜ ํฌ๊ธฐ๊ฐ€ ๋™์ผํ•œ ๋น„์œจ์„ ์œ ์ง€ํ•˜๋ฉฐ ์ปค์ง€์ง€ ์•Š๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

๐Ÿงฏ ํ•ด๊ฒฐ๋ฐฉ๋ฒ•

titleLabel์˜ widthAnchor๋ฅผ heightAnchor์˜ 3๋ฐฐ๋กœ ์„ค์ •ํ•˜์—ฌ ๋น„์œจ์„ ์ฃผ๊ณ  setContentCompressionResistancePriority๋กœ ๋†’์ด๊ฐ€ ์šฐ์„ ์ ์œผ๋กœ ๋Š˜์–ด๋‚  ์ˆ˜ ์žˆ๊ฒŒ ํ•˜์—ฌ ๊ธ€์”จ๊ฐ€ ์ปค์งˆ ๊ฒฝ์šฐ์—๋„ ๋น„์œจ์— ๋งž๊ฒŒ label์˜ ๋„ˆ๋น„๊ฐ€ ๋Š˜์–ด๋‚  ์ˆ˜ ์žˆ๋„๋ก ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

let titleLabelConstraint = titleLabel.widthAnchor.constraint(equalTo: titleLabel.heightAnchor, multiplier: 3)
titleLabel.setContentCompressionResistancePriority(.required, for: .vertical)


7. ๐Ÿ‘ฌ ํŒ€ ํšŒ๊ณ 

๐Ÿ˜ƒ ์ž˜ํ•œ ์ 

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

๐Ÿ˜ข ์•„์‰ฌ์› ๋˜ ์ 

  • Protocol์„ ๋งŽ์ด ํ™œ์šฉํ•˜์ง€ ๋ชปํ•œ ๋ถ€๋ถ„์ด ์•„์‰ฌ์› ์Šต๋‹ˆ๋‹ค.
  • POP์— ๋Œ€ํ•œ ๊ณ ๋ฏผ์„ ํ•˜์ง€ ๋ชปํ•œ ์ ์ด ์•„์‰ฌ์› ์Šต๋‹ˆ๋‹ค.

8. ๐Ÿ”— ์ฐธ๊ณ  ๋งํฌ

ios-box-office's People

Contributors

yarkyungmin avatar h-suo avatar soo941226 avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.