Skip to content

๐Ÿ’ฐ ์ฝฉ์ด, ์ถ”๋‹ˆ, ๋ชจ๋ชจ๊ฐ€ ๋งŒ๋“œ๋Š” Fakethumb ๐Ÿ’ฐ

Notifications You must be signed in to change notification settings

kch1285/Fake-Bithumb-Assignment

ย 
ย 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

๋ณธ ํ”„๋กœ์ ํŠธ๋Š” ๋น—์ธ iOS ์ฝ”์Šค ํ…Œํฌ ์บ ํ”„ 1๊ธฐ ๊ณผ์ œ๋กœ ์ง„ํ–‰๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

ํ”„๋กœ์ ํŠธ ๊ธฐ๊ฐ„ : 2022.02.21~2022.03.13 (3์ฃผ)


๐Ÿ˜Ž ๋ชฉ์ฐจ

์Šคํฌ๋ฆฐ์ƒท ๋ฐ ์˜์ƒ

๊ธฐ์ˆ  ์Šคํƒ

ํ”Œ๋กœ์šฐ ์ฐจํŠธ

ํŠธ๋Ÿฌ๋ธ” ์ŠˆํŒ…

๋งŒ๋“  ์‚ฌ๋žŒ๋“ค

์ปค๋ฎค๋‹ˆ์ผ€์ด์…˜ ๋ฐฉ์‹

์ฐธ๊ณ  ์ž๋ฃŒ


์Šคํฌ๋ฆฐ์ƒท ๋ฐ ์˜์ƒ

Fakethumb_video.mp4


๊ธฐ์ˆ  ์Šคํƒ


ํ”Œ๋กœ์šฐ ์ฐจํŠธ


ํŠธ๋Ÿฌ๋ธ” ์ŠˆํŒ…

์ฝฉ์ด

ํ”„๋กœ์ ํŠธ ์„ธํŒ…(REST API, Github Action)

  1. URL Session

: async/await์„ ๊ฒฝํ—˜ํ•ด๋ณด๊ธฐ ์œ„ํ•ด ๋ณ„๋„์˜ ๋„คํŠธ์›Œํฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ธฐ๋กœ ๊ฒฐ์ •ํ–ˆ๋‹ค. โ†’ await๋ฅผ ์“ฐ๋Š” ์ด์œ ๋Š” completionhandler๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์Œ์œผ๋กœ์จ ์˜ค๋ฅ˜ ์ฒ˜๋ฆฌ์˜ ๋ณต์žก์„ฑ์„ ์ค„์ด๊ณ , ํด๋กœ์ €๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์Œ์œผ๋กœ์จ ์ฝ”๋“œ ๊ฐ€๋…์„ฑ์„ ํ–ฅ์ƒํ•จ์œผ๋กœ์จ ํ•œ ์ค„๋กœ ๋น„๋™๊ธฐ ์ฝ”๋“œ๋ฅผ ๋งˆ์น˜ ๋™๊ธฐ ์ฝ”๋“œ์ฒ˜๋Ÿผ ๋ณด์ด๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•œ ๊ฒƒ.

แ„‰แ…ณแ„แ…ณแ„…แ…ตแ†ซแ„‰แ…ฃแ†บ 2022-02-24 แ„‹แ…ฉแ„’แ…ฎ 6 50 19

await/async๋Š” iOS 13.0๋ถ€ํ„ฐ ์ง€์›ํ•˜์ง€๋งŒ, async๋ฅผ ์ง€์›ํ•˜๋Š” URLSession API๋Š” iOS 15.0์ด ํ•„์š”ํ•˜๋‹ค.

แ„‰แ…ณแ„แ…ณแ„…แ…ตแ†ซแ„‰แ…ฃแ†บ 2022-02-24 แ„‹แ…ฉแ„’แ…ฎ 6 53 31

โ†’ ๊ทธ๋Ÿฐ๋ฐ ์ด์ „ ๋ฒ„์ „ ๋ถ€ํ„ฐ ๋Œ€์‘ํ•˜๊ฒŒ ๋˜๋ฉด ์ฝ”๋“œ ๊ฐ€๋…์„ฑ์˜ ์žฅ์ ์ด ์‚ฌ๋ผ์ ธ์„œ try await์„ ์‚ฌ์šฉํ•˜๋Š” ์˜๋ฏธ๊ฐ€ ์—†์–ด์ง€๊ฒŒ ๋œ๋‹ค.

โ†’ ๋”ฐ๋ผ์„œ Global ์ฝ”๋“œ๋Š” ๋‹ค๋ฅธ ์ฝ”๋“œ์—์„œ ์žฌ์‚ฌ์šฉ๋˜๋Š” ์ฝ”๋“œ๋“ค์„ ๋ชจ์•„๋†“์€ ํด๋”์ธ ๋งŒํผ, ๊ฐ„๊ฒฐํ•˜๊ณ  ๊น”๋”ํ•˜๊ฒŒ ์ž‘์„ฑํ•˜๊ณ  ์‹ถ๊ธฐ์— iOS 15๋ถ€ํ„ฐ ๋Œ€์‘ํ•˜๋„๋ก ์„ค์ •ํ–ˆ๋‹ค.

  1. CI

: ์‚ฌ์ „์— ๋นŒ๋“œ ์˜ค๋ฅ˜๋ฅผ ๋ฐœ๊ฒฌํ•˜์ง€ ๋ชปํ•œ ์ฑ„ main ๋ธŒ๋žœ์น˜์— mergeํ•˜๋Š” ์ƒํ™ฉ์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด CI ํˆด์„ ์ฑ„ํƒํ•˜๊ธฐ๋กœ ํ–ˆ๋‹ค.

โ†’ Github Action๊ณผ Jenkins๊ฐ€ ๊ฐ€์žฅ ์ผ๋ฐ˜์ ์œผ๋กœ ์‚ฌ์šฉ๋œ๋‹ค.

โ†’ Jenkins์˜ ๊ฒฝ์šฐ ๋ณ„๋„์˜ ์„œ๋ฒ„์— ํ˜ธ์ŠคํŒ…ํ•ด์•ผํ•˜๋Š” ์˜ค๋ฒ„ํ—ค๋“œ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฏ€๋กœ, Github์—์„œ ์„œ๋ฒ„๋ฅผ ์ œ๊ณตํ•˜๋Š” Github Action์„ ์‚ฌ์šฉ

  name: FakethumbBuildTest

  on:
  push:
  branches: [ main ]
  pull_request:
  branches: [ main ]

  jobs:
  build:

  runs-on: macos-latest

  steps:
  - uses: actions/checkout@v2
  - name: Run tests
    run: |
      pod install --repo-update --clean-install --project-directory=FakeBithumbAssignment/
      xcodebuild test -workspace ./FakeBithumbAssignment/FakeBithumbAssignment.xcworkspace -scheme FakeBithumbAssignment -destination 'platform=iOS Simulator,name=iPhone 11 Pro,OS=15.2'

CoinView ๋ฐ ํ˜ธ๊ฐ€ ์ •๋ณด์ฐฝ ๊ตฌํ˜„

  1. CoinView - UIPageViewController

: ๋ทฐ ์ปจํŠธ๋กค๋Ÿฌ ๋‚ด์—์„œ ๋˜ ๋‹ค๋ฅธ ๋ทฐ ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ๋ณด์—ฌ์ฃผ๊ธฐ ์œ„ํ•ด pageViewController๋ฅผ ์‚ฌ์šฉํ–ˆ๋‹ค.

โ†’ ๋ถ€๋ชจ ๋ทฐ ์ปจํŠธ๋กค๋Ÿฌ ๋‚ด์— pageViewController๊ฐ€ ์กด์žฌํ•˜๊ณ , pageViewController ์œ„์— ์›ํ•˜๋Š” child View Controller๋ฅผ ํ‘œ์‹œํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ๋™์ž‘ํ•œ๋‹ค.

โ†’ pageViewController๋Š” childViewController๋“ค์„ ๊ฐ€์ง€๊ณ  ์žˆ์œผ๋ฉฐ, delegate์™€ dataSource๋ฅผ ํ†ตํ•ด ํŽ˜์ด์ง€๋ฅผ ๊ด€๋ฆฌํ•œ๋‹ค.

   // pageViewController๋ฅผ ์„ค์ •
   private func setPageView() {
   self.pageViewController = CoinPagingViewController()
   
   if let pageViewController = pageViewController {
       self.addChild(pageViewController)
       self.pageView.addSubview(pageViewController.view)
       self.pageViewController?.view.snp.makeConstraints { make in
           make.top.leading.trailing.bottom.equalTo(self.pageView)
       }
       pageViewController.didMove(toParent: self)
   }
}

โ†’ ๋ถ€๋ชจ ๋ทฐ ์ปจํŠธ๋กค๋Ÿฌ์— addChild๋ฅผ ์ด์šฉํ•ด pageViewController๋ฅผ ๋ถ™์—ฌ์ค€ ํ›„, ๋ณด์—ฌ์งˆ pageView์—๋„ pageViewController์˜ view๋ฅผ addSubViewํ•œ๋‹ค.

โ†’ ๋ถ€๋ชจ ๋ทฐ ์ปจํŠธ๋กค๋Ÿฌ์— ์ƒˆ๋กœ์šด ์ปจํŠธ๋กค๋Ÿฌ๊ฐ€ ์ถ”๊ฐ€๋˜์—ˆ์œผ๋ฏ€๋กœ didMove()๋ฅผ ํ˜ธ์ถœํ•ด์ค€๋‹ค.

   // ํŽ˜์ด์ง€๋ฅผ ์ „ํ™˜ํ•˜๋Š” ์ฝ”๋“œ
   func setTabViewController(to type: TabView) {
     guard let page = pages[type] else { return }
     self.setViewControllers([page], direction: .forward, animated: false, completion: nil)
   }

โ†’ ์ดํ›„ pageViewController ๋‚ด๋ถ€์—์„œ setViewController ๋ฉ”์„œ๋“œ๋ฅผ ์ด์šฉํ•ด ํ™”๋ฉด ์ „ํ™˜ํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ๊ตฌํ˜„ํ•œ๋‹ค.

  1. ํ˜ธ๊ฐ€ ์ •๋ณด์ฐฝ - ์›น์†Œ์ผ“ (๋ชจ๋ชจ ๋ฆฌํŒฉํ† ๋ง ์ด์ „)

2-1. ๋ณ€๊ฒฝ๋œ ํ˜ธ๊ฐ€์˜ quantity๊ฐ€ 0์ธ ๊ฒฝ์šฐ, ํ˜„์žฌ ๋ฆฌ์ŠคํŠธ์—์„œ ํ•ด๋‹น price๋ฅผ ์ฐพ์•„ ์‚ญ์ œํ•ด์ค˜์•ผ ํ•œ๋‹ค.

```swift
if Double(quote.quantity) == 0 {
    self.removeQuantityIsZero(type: .ask, data: quote)
    continue
}

private func removeQuantityIsZero(type: BTSocketAPIResponse.OrderBookResponse.Content.OrderBook.OrderType,
                                  data: Quote) {
    switch type {
    case .ask:
        var count = self.asksList.count
        var index = 0
        while(index < count) {
            if Int(asksList[index].price) == Int(data.price) {
                self.asksList.remove(at: index)
                count -= 1
            }
            index += 1
        }
    case .bid:
        var count = self.bidsList.count
        var index = 0
        while(index < count) {
            if Int(bidsList[index].price) == Int(data.price) {
                self.bidsList.remove(at: index)
                count -= 1
            }
            index += 1
        }
    }
}
```

2-2. ๋‹จ์ˆœํžˆ ํ˜ธ๊ฐ€ ์ •๋ณด 30๊ฐœ์”ฉ ๋ฐ›์•„์„œ ํ‘œ์‹œํ•ด์ค„ ๊ฒฝ์šฐ, (1) ์ฒ˜๋Ÿผ remove๋œ ๊ฒฝ์šฐ์— ํ‘œ์‹œํ•ด์ค„ ๋ฐ์ดํ„ฐ๊ฐ€ ์—†๋‹ค.

-> ๊ทธ๋Ÿฌ๋‚˜ ๋น—์ธ์—์„œ ๋ฐ›์•„์˜ฌ ์ˆ˜ ์žˆ๋Š” ์ตœ๋Œ€ ๋ฐ์ดํ„ฐ๋Š” 30๊ฐœ์ด๋ฏ€๋กœ, ๊ทธ ์ด์ƒ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋กœ์ปฌ์—์„œ ๊ด€๋ฆฌํ•˜๊ณ  ๊ทธ ์ค‘์—์„œ 30๊ฐœ๋งŒ ํ‘œ์‹œํ•˜๋„๋ก ํ•ด์•ผํ•œ๋‹ค.

-> ๊ทธ๋ฆฌ๊ณ  ๋ฆฌ์ŠคํŠธ ์ค‘ asks(๋งค๋„)๋Š” ํ•˜์œ„ 30๊ฐœ, bids(๋งค์ˆ˜)๋Š” ์ƒ์œ„ 30๊ฐœ๋ฅผ ํ‘œ์‹œํ•œ๋‹ค. ๋ฌผ๋ก  price ์ˆœ์œผ๋กœ ์ •๋ ฌํ•ด์„œ.

```swift
private func sortQuoteList(type: BTSocketAPIResponse.OrderBookResponse.Content.OrderBook.OrderType) {
  switch type {
  case .ask:
      self.asksList = asksList.sorted(by: {$0.price > $1.price})
  case .bid:
      self.bidsList = bidsList.sorted(by: {$0.price > $1.price})
  }
}
```

2-3. ๋ณ€๊ฒฝ๋œ ํ˜ธ๊ฐ€๊ฐ€ ๋กœ์ปฌ ๋ฆฌ์ŠคํŠธ์— ์žˆ๋Š” ํ˜ธ๊ฐ€์™€ ๊ฐ™์€ ๊ฒฝ์šฐ, ํ•ด๋‹น ํ˜ธ๊ฐ€๋ฅผ ์ฐพ์•„ quantity๋ฅผ ์—…๋ฐ์ดํŠธ ํ•ด์ค€๋‹ค.

```swift
if !self.replaceQuote(type: .ask, data: quote) {
    self.asksList.append(quote)
}

private func replaceQuote(type: BTSocketAPIResponse.OrderBookResponse.Content.OrderBook.OrderType,
                          data: Quote) -> Bool {
    switch type {
    case .ask:
        let count = self.asksList.count
        var index = 0
        while(index < count) {
            if Int(asksList[index].price) == Int(data.price) {
                self.asksList[index] = data
                return true
            }
            index += 1
        }
    case .bid:
        let count = self.bidsList.count
        for index in 0..<count {
            if Int(self.bidsList[index].price) == Int(data.price) {
                self.bidsList[index] = data
                return true
            }
        }
    }
    return false
}
```

2-4. ๋กœ์ปฌ์— ์žˆ๋Š” list์—์„œ ์ฐพ์•„ removeํ•˜๋Š” ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๋„์ค‘, websocket api์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„์™€ list๊ฐ€ ์—…๋ฐ์ดํŠธ ๋˜๋ฉด ๋ฐ˜๋ณต๋ฌธ์˜ ๋ฒ”์œ„๊ฐ€ ์กฐ์ •๋˜์–ด index out of range ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

-> ๋”ฐ๋ผ์„œ remove ์ž‘์—…์„ ์‹œํ–‰ํ•˜๋Š” ๋™์•ˆ์—๋Š” ๋™๊ธฐ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด sempaphore๋ฅผ ํ™œ์šฉํ•œ๋‹ค.

```swift
private func updateTransactionData(coin: Coin,
                                   data: BTSocketAPIResponse.TransactionResponse) {
    let semaphore = DispatchSemaphore(value: 0)
    DispatchQueue.global(qos: .background).async {
        for transaction in data.content.list {
            switch transaction.buySellGb {
            case .sell:
                var count = self.asksList.count
                var index = 0
                while(index < count) {
                    if Double(self.asksList[index].price) == Double(transaction.contPrice) {
                        guard let quantity = Double(self.asksList[index].quantity) else { return }
                        if quantity - transaction.contQty <= 0 {
                            self.asksList.remove(at: index)
                            count -= 1
                        } else {
                            self.asksList[index].quantity = "\(quantity - transaction.contQty)"
                        }
                    }
                    index += 1
                }
            case .buy:
                var count = self.bidsList.count
                var index = 0
                while(index < count) {
                    if Double(self.bidsList[index].price) == Double(transaction.contPrice) {
                        guard let quantity = Double(self.bidsList[index].quantity) else { return }
                        if Double(self.bidsList[index].quantity)! - transaction.contQty <= 0 {
                            self.bidsList.remove(at: index)
                            count -= 1
                        } else {
                            self.bidsList[index].quantity = "\(quantity - transaction.contQty)"
                        }
                    }
                    index += 1
                }
            }
        }
        DispatchQueue.main.async {
            self.patchOrderbookData()
        }
        semaphore.signal()
    }
    semaphore.wait()
}
```

2-5. ๋งˆ์ง€๋ง‰์œผ๋กœ, ๊ฐ€๋” ๋งค๋„/๋งค์ˆ˜์˜ ๊ฒฝ๊ณ„์„ ์ด ์•„๋‹ˆ๋ผ ์•„์ฃผ ๋‚ฎ์€ ๊ฐ€๊ฒฉ์— ๋งค๋„๋ฅผ ํ•˜๊ฑฐ๋‚˜, ์•„์ฃผ ๋†’์€ ๊ฐ€๊ฒฉ์— ๋งค์ˆ˜๋ฅผ ํ•˜๋Š” ์ผ€์ด์Šค๊ฐ€ ์กด์žฌํ•œ๋‹ค.

-> ์ด๋Ÿฐ ๊ฒฝ์šฐ๋Š” ๋Œ€๊ฐœ ์ฒด๊ฒฐ์ด ๋˜์ง€๋งŒ, ์•„์ฃผ ๊ทน๋‹จ์ ์œผ๋กœ ์ƒ๊ฐํ•ด์„œ ๋งค๋„ํ•˜๋Š” ์‚ฌ๋žŒ๋งŒ ์žˆ๊ณ  ๋งค์ˆ˜ํ•˜๋Š” ์‚ฌ๋žŒ์€ ์—†๋Š” ๊ฒฝ์šฐ, ๋งค์ˆ˜ํ•˜๋Š” ์‚ฌ๋žŒ๋งŒ ์žˆ๊ณ  ๋งค๋„ํ•˜๋Š” ์‚ฌ๋žŒ๋งŒ ์žˆ๋Š” ๊ฒฝ์šฐ๋„ ์กด์žฌํ•  ์ˆ˜ ์žˆ๋‹ค. (์˜ˆ๋ฅผ ๋“ค์–ด.. ์ฃผ์‹์—์„œ ์ƒํ•œ๊ฐ€, ํ•˜ํ•œ๊ฐ€ ์ณค์„ ๋•Œ)

-> ์ฒด๊ฒฐ ์‹œ ๋กœ์ปฌ list์— ์žˆ๋Š” quantity - ์ฒด๊ฒฐ quanity = 0์ธ ๊ฒฝ์šฐ๋งŒ ํ˜ธ๊ฐ€์ฐฝ์—์„œ ์‚ญ์ œํ•˜๋„๋ก ํ•œ๋‹ค.
์ถ”๋‹ˆ

๋ฉ”์ธํ™”๋ฉด์˜ ํ—ค๋”๋ทฐ๋ฅผ ๊ตฌํ˜„ํ•  ๋•Œ, ์›ํ™”, ๊ด€์‹ฌ ๋ฒ„ํŠผ์€ ํ„ฐ์น˜๋˜๋ฉด ์•„๋ž˜์˜ ๋ฐ”๊ฐ€ ์ƒ๊ธฐ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ์ปฌ๋ ‰์…˜ ๋ทฐ์…€๋กœ ๊น”๋”ํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด ์›ํ™”, ๊ด€์‹ฌ ๋ฒ„ํŠผ์„ ์ปฌ๋ ‰์…˜ ๋ทฐ๋ฅผ ํ™œ์šฉํ•ด์„œ ๊ตฌํ˜„ํ•˜์˜€์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ ๋ชจ๋“  ์…€์— ๋Œ€ํ•ด ์ž๋™์ ์œผ๋กœ ๊ฐ„๊ฒฉ, ํฌ๊ธฐ ๋“ฑ์„ ์ง€์ •ํ•  ์ˆ˜ ์žˆ๊ณ , ์ถ”ํ›„์— ๋ฒ„ํŠผ์ด ์ถ”๊ฐ€๋  ์ˆ˜๋„ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์ปฌ๋ ‰์…˜ ๋ทฐ๋ฅผ ํ™œ์šฉํ•˜์˜€์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ปฌ๋ ‰์…˜ ๋ทฐ๋Š” ํ…Œ์ด๋ธ” ๋ทฐ์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ๋งŽ์€ ์–‘์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด์—ฌ์ฃผ๊ธฐ ์œ„ํ•ด ์กด์žฌํ•œ๋‹ค๊ณ  ์ƒ๊ฐ์ด ๋“ค์—ˆ์Šต๋‹ˆ๋‹ค. ํ˜„์žฌ์˜ 2๊ฐœ์˜ ๋ฒ„ํŠผ์„ ์ปฌ๋ ‰์…˜ ๋ทฐ๋กœ ๊ตฌํ˜„ํ•˜๊ธฐ์—๋Š” ๋ฐ์ดํ„ฐ๊ฐ€ ์ ๋‹ค๊ณ  ํŒ๋‹จ๋˜์—ˆ๊ณ , UIButton์œผ๋กœ ๊ตฌํ˜„ํ•˜๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

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

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

์‹ค์‹œ๊ฐ„ ๊ฐ€์ƒ ํ™”ํ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„ ํ…Œ์ด๋ธ” ๋ทฐ์— ๋‚˜ํƒ€๋‚ด๊ธฐ ์œ„ํ•ด ๋‹จ์ˆœํžˆ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์„ ๋•Œ, ํ…Œ์ด๋ธ” ๋ทฐ๋ฅผ reload์‹œํ‚ค์ž๋Š” ์ƒ๊ฐ์„ ํ•˜์˜€์Šต๋‹ˆ๋‹ค. ์ œ ์ƒ๊ฐ๊ณผ ๋‹ค๋ฅด๊ฒŒ ๊ธฐ๋ณธ์ ์ธ ํ…Œ์ด๋ธ”๋ทฐ๋ฅผ ์‚ฌ์šฉํ•˜์˜€์„ ๊ฒฝ์šฐ ๋งŽ์€ ์–‘์˜ ๋ฐ์ดํ„ฐ๊ฐ€ ์—ฐ์†์ ์œผ๋กœ ๋“ค์–ด์™€ ์•ฑ์ด ๋ฉˆ์ถ”๋Š” ํ˜„์ƒ์ด ๋ฐœ์ƒํ•˜์˜€์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด UITableViewDiffableDataSource๋ฅผ ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜์—ˆ๊ณ , iOS 15๋ถ€ํ„ฐ ๋„์ž…๋œ reconfigureItems ๋ฉ”์†Œ๋“œ๋ฅผ ํ†ตํ•ด ํ™”๋ฉด์˜ ๋Š๊น€ ์—†์ด ํ…Œ์ด๋ธ” ๋ทฐ๋ฅผ ์—…๋ฐ์ดํŠธํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

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

๋ชจ๋ชจ

์บ๋“ค์Šคํ‹ฑ ์ฐจํŠธ ๊ณผ๋ถ€ํ™” ๋ฌธ์ œ

  • ํ˜„์ƒ
    • ์บ”๋“ค์Šคํ‹ฑ ์ฐจํŠธ์—์„œ ์Šคํฌ๋กค์ด๋‚˜ ํ•€์น˜ ์ œ์Šค์ฒ˜๊ฐ€ ์žˆ์„ ๊ฒฝ์šฐ, ํ™”๋ฉด ์•ˆ์— ๋“ค์–ด์˜จ ์บ”๋“ค์Šคํ‹ฑ์„ ๋Œ€์ƒ์œผ๋กœ y์ถ• ์Šค์ผ€์ผ๋ง์ด ๋˜๋„๋ก ํ•˜๊ณ  ์‹ถ์—ˆ์œผ๋‚˜, ์ด๋Ÿฌํ•œ ์ƒํƒœ ๋ณ€ํ™”๊ฐ€ ์žˆ์—ˆ์„ ๊ฒฝ์šฐ ์•ฑ์ด ๊บผ์ง€์ง€ ์•Š๊ณ  ๋ฉˆ์ถ˜ ์ƒํƒœ๋กœ ์ง€์†๋˜๊ฑฐ๋‚˜, ํ™”๋ฉด ์ „ํ™˜์ด ๋Š๊ธฐ๋Š” ํ˜„์ƒ์ด ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. (CPU ์‚ฌ์šฉ๋ฅ  100%)
  • ์›์ธ
    • ์บ”๋“ค์Šคํ‹ฑ ์ฐจํŠธ๋Š” CALayer๋“ค์˜ ์กฐํ•ฉ์œผ๋กœ ๊ตฌ์„ฑ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๊ธธ์ด ์กฐ์ • ๋“ฑ์ด ํ•„์š”ํ•  ๊ฒฝ์šฐ์— ์ด ์ „์ฒด Layer๋“ค์„ ๋‹ค ์ง€์›Œ ์ค€ ๋’ค์— ๋‹ค์‹œ ์ถ”๊ฐ€ํ•ด ํ™”๋ฉด์„ ๊ทธ๋ ค์ฃผ๋Š” ์—ฐ์‚ฐ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ๋งค ์ด๋ฒคํŠธ(์Šคํฌ๋กค, ํ•€์น˜) ๋งˆ๋‹ค ๋ชจ๋“  ์บ”๋“ค์Šคํ‹ฑ์— ๋Œ€ํ•ด ์ด๋Ÿฌํ•œ ์ฒ˜์Œ๋ถ€ํ„ฐ ๋‹ค์‹œ ๊ทธ๋ ค์ฃผ๋Š” ์—ฐ์‚ฐ์ด ๋ฐ˜๋ณต๋˜๊ณ  ์žˆ์–ด ์ด๋Ÿฌํ•œ ๋ฐฉ๋Œ€ํ•œ ์—ฐ์‚ฐ์ด ์›์ธ์ด์˜€์Šต๋‹ˆ๋‹ค.
  • ํ•ด๊ฒฐ
    • ๊ทธ๋ฆฌ๊ธฐ ์—ฐ์‚ฐ์„ ์ตœ์†Œํ™” ํ•ด CPU ๋ถ€ํ•˜๋ฅผ ๋”๋Š” ๋ฐฉ๋ฒ•์œผ๋กœ ํ•ด๊ฒฐ ํ–ˆ์Šต๋‹ˆ๋‹ค. ๋ชจ๋“  ๋ฉ”์†Œ๋“œ๊ฐ€ O(N)์˜ ์‹œ๊ฐ„ ๋ณต์žก๋„๋ฅผ ๊ฐ–๊ณ  ์žˆ์–ด 2์ฐจ์›์˜ ๋ ˆ์ด์–ด ์—ฐ์‚ฐ์€ ํฐ ๋ถ€๋‹ด์ด ๋˜์ง€ ์•Š์„ ๊ฒƒ์ด๋ผ ์ƒ๊ฐํ–ˆ์ง€๋งŒ ์ด๋ฒคํŠธ๋งˆ๋‹ค 5000๊ฐœ์ •๋„์˜ ๋ ˆ์ด์–ด ์‚ญ์ œ ํ›„ ์ถ”๊ฐ€๋Š” ์ƒ๋‹นํ•œ ๋ถ€๋‹ด์ด ๋˜์—ˆ๋˜ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. ๊ธฐ์กด์—๋Š” ์บ”๋“ค์Šคํ‹ฑ ์ฒ˜์Œ๋ถ€ํ„ฐ ๋๊นŒ์ง€ ๊ฐ๊ฐ์˜ ๋ ˆ์ด์–ด๋ฅผ ๋‹ค ์ถ”๊ฐ€ํ•ด์ฃผ๊ณ  ์ง€์›Œ์ฃผ๋Š” ๋ฐฉ์‹์œผ๋กœ ๊ฐœ๋ฐœ์ด ๋˜์–ด์žˆ์—ˆ์œผ๋‚˜, ๊ผญ ๊ทธ๋ ค์ค˜์•ผ ํ•˜๋Š” ์บ”๋“ค์Šคํ‹ฑ๋งŒ ๋Œ€์ƒ์œผ๋กœ ๋ ˆ์ด์–ด๋ฅผ ์ถ”๊ฐ€ ํ•ด์ฃผ๋„๋ก ์ˆ˜์ • ํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ผญ ๊ทธ๋ ค์ค˜์•ผ ํ•˜๋Š” ์บ”๋“ค์Šคํ‹ฑ์˜ ์˜์—ญ์€ ์Šคํฌ๋กค ๋ทฐ์˜ contentOffset์œผ๋กœ ๊ณ„์‚ฐ์„ ํ•ด ์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค. ๋ฐ˜๋ณต์ ์œผ๋กœ ํ•„์š”ํ•œ ๊ฐ’๋“ค์€ computed property๋ฅผ ํ†ตํ•˜์ง€ ์•Š๊ณ  stored property์— ํ•œ๋ฒˆ๋งŒ ๊ณ„์‚ฐ ํ•ด์„œ ๋„ฃ์–ด์ฃผ๋Š” ๋ฐฉ๋ฒ•์œผ๋กœ๋„ ์—ฐ์‚ฐ์„ ์ตœ์†Œํ™” ํ–ˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ ๋ ˆ์ด์–ด ์ž‘์—…์— ์ž๋™์œผ๋กœ ์• ๋‹ˆ๋ฉ”์ด์…˜ ํšจ๊ณผ๊ฐ€ ๋“ค์–ด๊ฐ€ ์žˆ์–ด ๋ ˆ์ด์–ด ์ „์ฒด๋ฅผ CATransaction์œผ๋กœ ๋ฌถ์–ด ์• ๋‹ˆ๋ฉ”์ด์…˜ ํšจ๊ณผ๋„ ์ œ๊ฑฐ ํ•ด ์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค.
  • ๊ฒฐ๊ณผ
    • ์›ํ•˜๋˜ ๋Œ€๋กœ ์Šคํฌ๋กค, ํ•€์น˜ ์ œ์Šค์ฒ˜ ์‹œ ์ ์ ˆํ•œ ์Šค์ผ€์ผ์˜ ์บ”๋“ค์Šคํ‹ฑ ์ฐจํŠธ๊ฐ€ ํ™”๋ฉด์— ๊ฐ€๋“ ์ฐจ๋„๋ก ์ž‘๋™ ํ–ˆ์Šต๋‹ˆ๋‹ค.

Core Data ์‚ฌ์šฉ ์‹œ ์•ฑ ํฌ๋ž˜์‹œ ๋ฌธ์ œ

  • ํ˜„์ƒ
    • ์บ”๋“ค์Šคํ‹ฑ ์ฐจํŠธ์—์„œ ์ฝ”์–ด๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜๋Š”๋ฐ, ๊ฐ„๊ฐ„ํžˆ ์•ฑ ํฌ๋ž˜์‹œ๊ฐ€ ๋ฐœ์ƒ [error] error: Serious application error. Exception was caught during Core Data change processing.
  • ์›์ธ
    • NSManagedObjectContext๋Š” ์“ฐ๋ ˆ๋“œ ํ•˜๋‚˜์—์„œ๋งŒ ๋™์ž‘ํ•˜๋„๋ก ์ •์ฑ…์ด ์ •ํ•ด ์ ธ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ์“ฐ๋ ˆ๋“œ ์•ˆ์ „ํ•˜์ง€ ์•Š์€ ๊ฐ์ฒด์ด๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. ๋ฉ”์ธ ์“ฐ๋ ˆ๋“œ์—์„œ๋งŒ ๋™์ž‘ํ•˜๋„๋ก ์ •ํ•ด์ ธ ์žˆ๋Š” persistentContainer.viewContext ๋ฅผ ๋„คํŠธ์›Œํฌ ์š”์ฒญ ๋“ฑ์œผ๋กœ ์ธํ•ด ๋ฉ”์ธ ์“ฐ๋ ˆ๋“œ๊ฐ€ ์•„๋‹Œ ๊ณณ์—์„œ ์ฝ”์–ด ๋ฐ์ดํ„ฐ์— ์ ‘๊ทผ์„ ํ•˜๊ฒŒ ๋˜๋ฉด ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ MOC์— ์ ‘๊ทผํ•  ๋•Œ์—๋Š” ํ•ญ์ƒ ๊ฐ™์€ ์“ฐ๋ ˆ๋“œ๊ฐ€ ๋˜๋„๋ก ์‹ ๊ฒฝ์„ ์จ์ฃผ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  • ํ•ด๊ฒฐ
    • concurrencyType์„ ์ง€์ •ํ•  ์ˆ˜ ์žˆ๋Š” NSManagedObjectContext ์ƒ์„ฑ์ž๋ฅผ ์ด์šฉํ•ด ์ƒ์„ฑํ•ด ์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค. NSPrivateQueueConcurrencyType **์€ ์ž์‹ ๋งŒ์˜ ํ๋ฅผ ๊ฐ–๊ณ  ์žˆ์–ด **NSManagedObjectContext ์˜ ์—ฐ์‚ฐ๋“ค์„ ํ•ด๋‹น ํ์—์„œ ๋™์ž‘ํ•˜๋„๋ก ๊ด€๋ฆฌ ํ•ด ์ค๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์“ฐ๋ ˆ๋“œ ํ•˜๋‚˜์—์„œ๋งŒ ๋™์ž‘ํ•˜๋„๋ก ์ •ํ•ด์ง„ ์ •์ฑ…์„ ์œ„๋ฐ˜ํ•˜์ง€ ์•Š๊ณ , ์•ฑ ํฌ๋ž˜์‹œ๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • ๊ฒฐ๊ณผ
    • Core Data๋ฅผ ์•ฑ ํฌ๋ž˜์‹œ ์—†์ด ์ž˜ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

Ground Rules

Git branch
  • git flow๋ฅผ ๋”ฐ๋ฅธ๋‹ค.
  • main ๋ธŒ๋žœ์น˜๋ฅผ default ๋ธŒ๋žœ์น˜๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
  • ๊ธฐ๋Šฅ ๋ณ„๋กœ feaure ๋ธŒ๋žœ์น˜๋ฅผ ์ƒ์„ฑํ•˜๊ณ , main ๋ธŒ๋žœ์น˜์— mergeํ•ฉ๋‹ˆ๋‹ค.
  • feature ๋ธŒ๋žœ์น˜์˜ ๋„ค์ด๋ฐ์€ feature/์ด์Šˆ๋ฒˆํ˜ธ-๊ธฐ๋Šฅ์ด๋ฆ„ ์œผ๋กœ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
์ฝ”๋“œ ์ปจ๋ฒค์…˜
  • Swift API Design GuideLine ๋”ฐ๋ฆ…๋‹ˆ๋‹ค.

  • ์ด์™ธ์˜ ๋ฃฐ์— ๋Œ€ํ•ด์„œ๋Š” StyleShare์˜ Swift-Style-GuideLine์„ ๋”ฐ๋ฆ…๋‹ˆ๋‹ค.

    -> ๋‹จ, ๋‹ค์Œ์˜ ๋ฃฐ์€ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.

    1)  ๋“ค์—ฌ์“ฐ๊ธฐ์—๋Š” ํƒญ(tab) ๋Œ€์‹  2๊ฐœ์˜ space๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
    2)  ํƒ€์ž… ์ฒซ ์ค„ ๋„์–ด์“ฐ๊ธฐ ์—†์ด ๋ถ™์—ฌ์“ฐ๊ธฐ
  • MARK ์ฃผ์„์„ ํ™œ์šฉํ•ฉ๋‹ˆ๋‹ค.

    // MARK: - @IBOutlets Action
    // MARK: - @IBOutlet Outlets
    // MARK: - Life Cycle func
    // MARK: - custom func
    // MARK: - extension์œผ๋กœ ๋นผ๊ณ  ์–ด๋–ค ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜๋Š”์ง€ ์ ๊ธฐ
    // MARK: - @objc
    // MARK: - Type Property
    // MARK: - Instance Property
    // MARK: - Initializer
์ปค๋ฐ‹ ์ปจ๋ฒค์…˜
# [ํƒ€์ž…] : ์ œ๋ชฉ (#์ด์Šˆ๋ฒˆํ˜ธ)

##### ์ œ๋ชฉ์€ ์ตœ๋Œ€ 50 ๊ธ€์ž๊นŒ์ง€๋งŒ ์ž…๋ ฅ ############## -> |

# ๋ณธ๋ฌธ์€ ์œ„์— ์ž‘์„ฑ
######## ๋ณธ๋ฌธ์€ ํ•œ ์ค„์— ์ตœ๋Œ€ 72 ๊ธ€์ž๊นŒ์ง€๋งŒ ์ž…๋ ฅ ########################### -> |

# --- COMMIT END ---
# [ํƒ€์ž…] ๋ฆฌ์ŠคํŠธ
#   FEAT    : ๊ธฐ๋Šฅ (์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ) -> ๊ธฐ๋Šฅ์ ์ธ ์ˆ˜์ •
#   FIX     : ๋ฒ„๊ทธ (๋ฒ„๊ทธ ์ˆ˜์ •)
#   REFACTOR: ๋ฆฌํŒฉํ† ๋ง -> ์ฝ”๋“œ ์žฌ๋ฐฐ์น˜์™€ ๊ฐ™์€, ๊ธฐ๋Šฅ์ ์ธ ์ˆ˜์ •
#   STYLE   : ์Šคํƒ€์ผ (์ฝ”๋“œ ํ˜•์‹, ์„ธ๋ฏธ์ฝœ๋ก  ์ถ”๊ฐ€: ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์— ๋ณ€๊ฒฝ ์—†์Œ)
#   DOCS    : ๋ฌธ์„œ (๋ฌธ์„œ ์ถ”๊ฐ€, ์ˆ˜์ •, ์‚ญ์ œ)
#   TEST    : ํ…Œ์ŠคํŠธ (ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ถ”๊ฐ€, ์ˆ˜์ •, ์‚ญ์ œ: ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์— ๋ณ€๊ฒฝ ์—†์Œ)
#   CHORE   : ๊ธฐํƒ€ ๋ณ€๊ฒฝ์‚ฌํ•ญ (๋นŒ๋“œ ์Šคํฌ๋ฆฝํŠธ ์ˆ˜์ • ๋“ฑ)
# ------------------
#     ํƒ€์ž…์€ ๋Œ€๋ฌธ์ž๋กœ
#     ์ œ๋ชฉ์€ ๋ช…๋ น๋ฌธ์œผ๋กœ
#     ์ œ๋ชฉ ๋์— ๋งˆ์นจํ‘œ(.) ๊ธˆ์ง€
#     ์ œ๋ชฉ๊ณผ ๋ณธ๋ฌธ์„ ํ•œ ์ค„ ๋„์›Œ ๋ถ„๋ฆฌํ•˜๊ธฐ
#     ๋ณธ๋ฌธ์€ "์–ด๋–ป๊ฒŒ" ๋ณด๋‹ค "๋ฌด์—‡์„", "์™œ"๋ฅผ ์„ค๋ช…ํ•œ๋‹ค.
#     ๋ณธ๋ฌธ์— ์—ฌ๋Ÿฌ์ค„์˜ ๋ฉ”์‹œ์ง€๋ฅผ ์ž‘์„ฑํ•  ๋• "-"๋กœ ๊ตฌ๋ถ„
# ------------------
ํด๋”๋ง ์ปจ๋ฒค์…˜
FakethumbAssignment
  |
  โ””โ”€โ”€ FakethumbAssignment
		   |โ”€โ”€ Global
		   โ”‚   โ”‚โ”€โ”€ Literal 
		   โ”‚   โ”‚โ”€โ”€ Base 
		   โ”‚   โ”‚โ”€โ”€ Protocol
		   โ”‚   โ”‚โ”€โ”€ Supports
		   โ”‚   โ”‚      โ”‚โ”€โ”€ AppDelegate
                   โ”‚   โ”‚      โ”‚โ”€โ”€ SceneDelegate
		   โ”‚   โ”‚      โ””โ”€โ”€ Info.plist
		   โ”‚   โ”‚โ”€โ”€ Utils
		   โ”‚   โ”‚โ”€โ”€ Extension
		   โ”‚   โ”‚โ”€โ”€ UIComponent
		   โ”‚   โ””โ”€โ”€ Resource
		   โ”‚          โ””โ”€โ”€ Assets.xcassets
		   โ”‚
  	           |โ”€โ”€ Network
		   โ”‚   โ”‚โ”€โ”€ APIService 
		   โ”‚   โ”‚โ”€โ”€ API  
	           โ”‚   โ”‚โ”€โ”€ Model
		   โ”‚   โ””โ”€โ”€ Foundation
		   โ”‚
		   โ””โ”€โ”€ Screens 
		       โ””โ”€โ”€ Main
		            โ””โ”€โ”€ View
์ฝ”๋“œ ๋ฆฌ๋ทฐ ๊ทœ์น™
 1) merge ๊ธฐ์ค€
   : 2๋ช… ๋ชจ๋‘ approveํ•˜๋ฉด mergeํ•œ๋‹ค.
 2) ๋ฆฌ๋ทฐ ์‹œ๊ฐ„
   : ๋งˆ์ง€๋ง‰ comment๋กœ๋ถ€ํ„ฐ ํ•˜๋ฃจ๊ฐ€ ์ง€๋‚˜๋ฉด approveํ•œ๋‹ค.
   : ์ž‘์—… ์ง€์—ฐ์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด 24์‹œ๊ฐ„์ด ์ง€๋‚˜๋ฉด ๋ณธ์ธ์ด mergeํ•œ๋‹ค.

๋งŒ๋“  ์‚ฌ๋žŒ๋“ค

์ฝฉ์ด(beansbin) ์ถ”๋‹ˆ(chuuny) ๋ชจ๋ชจ(momo-youngg)
ํ”„๋กœ์ ํŠธ ๊ธฐ๊ฐ„ ๋™์•ˆ ์›น์†Œ์ผ“๊ณผ ๋น„๋™๊ธฐ ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฒ•, ๊ทธ๋ฆฌ๊ณ  ์Šคํƒ๋ทฐ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ๊ฒŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ํ˜‘์—…ํ•˜๋Š” ๊ธฐ๊ฐ„ ๋™์•ˆ ์—ฌ๋Ÿฌ ์ž‘์€ ํŠธ๋Ÿฌ๋ธ”์ด ์žˆ์—ˆ์ง€๋งŒ, ๊ฒฐ๊ตญ์—” ํ•จ๊ป˜ ํ•ด๊ฒฐํ•ด๋‚˜๊ฐ€๋Š” ๋ชจ์Šต์ด ๋ฉ‹์ง„ ํŒ€์›๋“ค์ด์—ˆ์–ด์š”! 3์ฃผ ์ „๋ณด๋‹ค ํ•œ ๋ฐœ ์„ฑ์žฅํ•œ ์šฐ๋ฆฌ์˜ ๋ชจ์Šต์ด ๊ฝค๋‚˜ ์ž๋ž‘์Šค๋Ÿฝ์Šต๋‹ˆ๋‹ค ๐Ÿ˜Ž ๊ณ ์ƒ๋งŽ์œผ์…จ์Šต๋‹ˆ๋‹ค! ๋‹ค๋“ค ํ–‰๋ณตํ•˜์„ธ์š”! ํ˜‘์—…ํ•˜๊ธฐ ์ข‹์€ ํŒ€์›๋“ค์„ ๋งŒ๋‚˜ ํ”„๋กœ์ ํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๋ฉฐ ์†Œํ†ตํ•˜๊ณ  ํ˜‘์—…ํ•  ์ˆ˜ ์žˆ์–ด ์ •๋ง ๊ฐ’์ง„ ๊ฒฝํ—˜์ด์—ˆ์Šต๋‹ˆ๋‹ค. ํฐ ํŠธ๋Ÿฌ๋ธ” ์—†์ด ํ”„๋กœ์ ํŠธ๋ฅผ ์™„์„ฑํ•˜๊ฒŒ ๋˜์–ด์„œ ๊ธฐ์ฉ๋‹ˆ๋‹ค. ๋ถ€์กฑํ•œ ์ €์™€ ํ•จ๊ป˜ ํ•ด์ฃผ์…”์„œ ๊ณ ์ƒ๋งŽ์œผ์…จ๊ณ  ๋„ˆ๋ฌด ๊ฐ์‚ฌํ–ˆ์Šต๋‹ˆ๋‹ค! ๋‹ค๋“ค ์ •๋ง ์ˆ˜๊ณ ๋งŽ์œผ์…จ์Šต๋‹ˆ๋‹ค. ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ์ €์˜ ์ฒซ ์•„์ด๊ฐ€ ๋“œ๋””์–ด ์™„์„ฑ์ด ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. 99.9% ์™„๋ฒฝ๋„๋ฅผ ์ž๋ž‘ํ•˜๊ณ  ์žˆ์œผ๋‹ˆ ์ด์˜๊ฒŒ ๋ด์ฃผ์„ธ์š”~! (์บ”๋“ค์Šคํ‹ฑ ์ฐจํŠธ๊ฐ€ ์•„์ฃผ ๊ธฐ๋˜ฅ์ฐน๋‹ˆ๋‹ค) ๋ฉ‹์ง„ ํŒ€์›๋“ค ๊ณ ์ƒ ๋งŽ์œผ์…จ์–ด์š”~! ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย  ย ย 

์ปค๋ฎค๋‹ˆ์ผ€์ด์…˜ ๋ฐฉ์‹

  • ํ”„๋กœ์ ํŠธ ์ผ์ • ๊ด€๋ฆฌ : Notion
  • ์Šคํฌ๋Ÿผ ๋ฏธํŒ… ๋ฐ ์†Œํ†ต : Discord
  • Webhook : Discord

์ฐธ๊ณ  ์ž๋ฃŒ

๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ Version
Starscream 4.0.0 CocoaPods
SnapKit 5.0.0 CocoaPods
Then 2.7.0 CocoaPods

About

๐Ÿ’ฐ ์ฝฉ์ด, ์ถ”๋‹ˆ, ๋ชจ๋ชจ๊ฐ€ ๋งŒ๋“œ๋Š” Fakethumb ๐Ÿ’ฐ

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Swift 99.8%
  • Ruby 0.2%