๐ŸŒฟ ์‹๋ฌผ ๋ฌผ์ฃผ๊ธฐ ์•Œ๋ฆผ ์–ดํ”Œ, Jobcho(์žก์ดˆ) ๐ŸŒต

baecheeseยท2020๋…„ 8์›” 26์ผ
0

๊ฐœ์ธ ํ”„๋กœ์ ํŠธ

๋ชฉ๋ก ๋ณด๊ธฐ
1/1
post-thumbnail

๋ฐ”๊นฅ์—์„œ ์ž๋ผ๋Š” ์žก์ดˆ๋“ค์€ ์–ด๋””์„œ๋“  ์ž˜ ์ž๋ผ๋˜๋ฐ, ์ง‘์— ์žˆ๋Š” ์‹๋ฌผ๋“ค์€ ์™œ์ด๋ ‡๊ฒŒ ์•ฝํ•˜๊ณ  ์†์ด ๋งŽ์ด ๊ฐˆ๊นŒ.

์–ผ๋งˆ ์ „, ์—ญ ์•ž์—์„œ ์‚ฌ์˜จ ํ…Œ์ด๋ธ”์•ผ์ž์™€ ์ŠคํŒŒํ‹ฐํ•„๋ฆ„์ด ์‹œ๋“ค์‹œ๋“คํ•ด์ง€๊ธฐ ์‹œ์ž‘ํ–ˆ๋‹ค. ์ถœํ‡ด๊ทผ์„ ํ•˜๋‹ค๋ณด๋ฉด ์•„๋ฌด๋ž˜๋„ ๋ฌผ ์ฃผ๋Š” ์ฃผ๊ธฐ๋ฅผ ์ž์ฃผ ์žŠ๊ฒŒ ๋˜๋‹ค๋ณด๋‹ˆ ์–ด๋–ค ๋•Œ๋Š” ํ™์ด ๋งˆ๋ฅด๋„๋ก ๋ฌผ์„ ์ฃผ์ง€ ์•Š๊ณ , ์–ด๋–ค ๋•Œ๋Š” ์ฃผ๊ณ ๋„ ๊นŒ๋จน๊ณ  ๋˜ ์ฃผ๊ธฐ๋„ ํ•˜๋Š” ๋ถˆ์ƒ์‚ฌ๊ฐ€ ์ผ์–ด๋‚˜๊ธฐ๋„ ํ•œ๋‹ค. (์‹๋ฌผ ์ž…์žฅ์—์„œ๋„ ๋ถˆ๋งŒ์ด ์ด๋งŒ์ €๋งŒ์ด ์•„๋‹ ๊ฑฐ๋‹ค..)

์ด๋Ÿฌ์ €๋Ÿฌํ•œ ์ด์œ ๋กœ ๋‚จํŽธ์—๊ฒŒ ์‹๋ฌผ ๋ฌผ์ฃผ๊ธฐ ์—…๋ฌด๋ฅผ ์ธ์ˆ˜์ธ๊ณ„ ํ•˜๊ณ ์ž ํ–ˆ๋Š”๋ฐ, ๋‚จํŽธ์€ ๋ณธ์ธ์ด ์›ํ•˜๋Š” ๊ฐ„๋‹จํ•œ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋งŒ๋“ค์–ด๋‹ฌ๋ผ๊ณ  ์š”๊ตฌํ–ˆ๋‹ค.

๊ธฐํš์„ ์ข…์ด์žฅ์— ํ•ด์ฃผ๊ธธ๋ž˜ ์ •๋ฆฌํ•˜๊ณ  ๊ตฌ๊ฒจ๋ฒ„๋ฆผ ^^^

์ •๋ฆฌํ•˜์ž๋ฉด ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

1. ์‹๋ฌผ ์ •๋ณด CRUD
2. ์‹๋ฌผ ์ฃผ์š” ์ •๋ณด๋ฅผ ๋ฆฌ์ŠคํŠธ๋กœ ํ•œ ๋ˆˆ์— ์‰ฝ๊ฒŒ ๋ณผ ์ˆ˜ ์žˆ์–ด์•ผ ํ•œ๋‹ค.
   - ์ด๋ฆ„ / ์‚ฌ์ง„ / ๋ฌผ์ฃผ๋Š” ๋‚  D-Day 
3. โ€˜๋ฌผ์ฃผ๊ธฐโ€™๋ฅผ ๋ฆฌ์ŠคํŠธ์—์„œ ๋ฐ”๋กœ ํ•  ์ˆ˜ ์žˆ์–ด์•ผ ํ•œ๋‹ค.
4. ๋ฌผ์ฃผ๋Š” ๋‚ ์„ ์žŠ์ง€ ์•Š๊ฒŒ ์•Œ๋ฆผ์ด ์žˆ์–ด์•ผ ํ•œ๋‹ค.
  - ํ•˜๋ฃจ ์ „ AM 10:00
  - ๋‹น์ผ PM 12:00
4. ๋ฌผ ์ค€ ๊ธฐ๋ก๋„ ์ง€๋‚œ 10์ผ ์ •๋„ ์žˆ์œผ๋ฉด ์ข‹๊ฒ ๋‹ค.

๊ธฐ๋Šฅ์€ ๊ฐ„๋‹จํ•ด์„œ ๊ฐœ๋ฐœ์€ ์ฃผ๋ง๋™์•ˆ ์™„๋ฃŒ๋ฅผ ํ–ˆ๊ณ , ์•ฑ์Šคํ† ์–ด ์˜ฌ๋ ค์•ผํ•  ์„ค์ • (์•ฑ ๋“ฑ๋ก, ์Šคํฌ๋ฆฐ์ƒท ์ œ์ž‘ ๋“ฑ)์— ์•ฝ ํ•˜๋ฃจ ์ •๋„ ๊ฑธ๋ฆฐ ๋“ฏ ํ•˜๋‹ค. ์•ฑ์Šคํ† ์–ด ์‹ฌ์‚ฌ๊นŒ์ง€ ํ•ด์„œ ์ด 3-4์ผ ์ •๋„ ์†Œ์š”๋˜์—ˆ๋‹ค. (ํ•œ๋ฒˆ ์นด๋ฉ”๋ผ ๋ฉ”์„ธ์ง€ ๋Œ€์ถฉ ์จ์„œ ๋ฆฌ์ ๋จ -ใ…‚-..)

๊ฐœ๋ฐœ ์ŠคํŽ™

  • Swift 5.0
  • MVVM
  • database: CoreData
  • CD: fastlane
  • animation: lottie

๊ฐœ๋ฐœ ์ด์Šˆ

๋ฌผ์ฃผ๋Š” ์ฃผ๊ธฐ Date ๊ตฌํ•˜๊ธฐ

๊ฐ€์žฅ ์ฃผ์š” ๊ธฐ๋Šฅ์ธ ์•Œ๋ฆผ ์‹œ๊ฐ„์— ๋Œ€ํ•ด์„œ๋Š” Calendar๋ฅผ ์ด์šฉํ•ด์„œ ์‰ฝ๊ฒŒ ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค. (์ฒ˜์Œ์—๋Š” ๋‹ค์Œ ์‹œ๊ฐ„๊นŒ์ง€์˜ TimeInterval์„ ๊ณ„์‚ฐํ•˜๋ ค๊ณ  ํ•˜๊ณ  ์žˆ์—ˆ์Œ...)

/// ์ž์ฃผ ์“ฐ๋Š” ๋‚ ์งœ ๊ณ„์‚ฐ์— ๋Œ€ํ•ด์„œ๋Š” extension์œผ๋กœ ๋งŒ๋“ค์–ด ๋†“๋Š”๊ฒŒ ํŽธํ•˜๋‹ค.
extension TimeInterval {
    
    func plusDay(dayAmount:Int) -> TimeInterval {
        let plusSecondsAmount = (TimeInterval)((60 * 60 * 24) * dayAmount)
        return self + plusSecondsAmount
    }
    
    func minusDay(dayAmount:Int) -> TimeInterval {
        let minusSecondsAmount = (TimeInterval)((60 * 60 * 24) * dayAmount)
        return self - minusSecondsAmount
    }
    
}

protocol PlantPresentable {
    
    var image: UIImage? { get set }
    var name: String { get set }
    var startAt: Date { get set }
    var lastWateringAt: Date? { get set }
    var period: Int { get set }
    var wateringDateRecord: [Date]? { get }
}

extension PlantPresentable {
    
    var needWateringDay: Date {
    	 // ๋งˆ์ง€๋ง‰์œผ๋กœ ๋ฌผ ์ค€ ๋‚ ์งœ + ๋ฌผ ์ค˜์•ผ ํ•˜๋Š” ์ฃผ๊ธฐ(period) Day๋ฅผ ๋”ํ•˜๊ณ 
        let needTimeInterval = (lastWateringAt ?? startAt).timeIntervalSince1970.plusDay(dayAmount: period)
        return Calendar.current
            .date(
                bySettingHour: 10, minute: 0, second: 0,// ํ•ด๋‹น ๋‚ ์งœ์—์„œ์˜ ์›ํ•˜๋Š” ์‹œ๊ฐ„์„ ์„ค์ •ํ•ด์„œ Date๋ฅผ ๊ตฌํ•œ๋‹ค.
                of: Date(timeIntervalSince1970: needTimeInterval)
        ) ?? Date()
    }
    
    // ์œ„์—์„œ ๊ตฌํ•œ ๋‚ ์งœ์— ํ•˜๋ฃจ ์ „๋‚ ์ž„
    var needWateringAgoDate: Date {
        return Calendar.current
            .date(
                bySettingHour: 12, minute: 0, second: 0,
                of: Date(timeIntervalSince1970: needWateringDay.timeIntervalSince1970.minusDay(dayAmount: 1))
        ) ?? Date()
    }
    
}

๊ธฐ์–ต์— ๋‚จ๋Š” ์—๋Ÿฌ

ํฐ ๋ฌธ์ œ๊ฐ€ ๋ฌ๋˜๊ฑด ์—†์—ˆ๊ณ , ๊ธฐ์–ต์— ๋‚จ๋Š” ์—๋Ÿฌ๋Š” ๋กœ์ปฌ ๋…ธํ‹ฐํ”ผ์ผ€์ด์…˜์„ ๋“ฑ๋กํ•˜๋Š”๋ฐ ์ ‘๊ทผ ๊ถŒํ•œ ๋“ฑ๋ก/๊ฑฐ์ ˆ ํ›„์— ์ด๋ฒคํŠธ์— ๋Œ€ํ•ด
Main Thread Checker: UI API called on a background thread: -[UIApplication applicationState]
์ด๋Ÿฐ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋˜ ๊ฒƒ.

์•ฑ์ด ํ•œ์ฐธ lock ์ƒํƒœ์— ๋น ์กŒ๋‹ค๊ฐ€ ์ด๋ฒคํŠธ๊ฐ€ ์‹คํ–‰๋˜์—ˆ๋‹ค.

์ด ๋ถ€๋ถ„์€ main thread๋กœ ์ด๋ฒคํŠธ๋ฅผ ๋Œ๋ฆฌ๋Š” ๋ฐฉ์‹์œผ๋กœ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐ ํ–ˆ๋‹ค.

class LocalNotificationManager {
    
    func requestPermission(onAccess: @escaping () -> Void) {
        UNUserNotificationCenter
            .current()
            .requestAuthorization(options: [.alert]) { granted, error in
                if false == granted {
                    DispatchQueue.main.async {
                        UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!, options: [:], completionHandler: nil)
                    }
                    return
                }
                guard granted == true && error == nil else { return }
                onAccess()
        }
    }
 
  (...์ƒ๋žต...)
 
}

ref: https://stackoverflow.com/questions/44767778/main-thread-checker-ui-api-called-on-a-background-thread-uiapplication-appli

๊ฒฐ๊ณผ

ํœด๊ฐ€ ๊ธฐ๊ฐ„์ด๋ผ ๋†€๋ฉด์„œ(?) ๋น ๋ฅด๊ฒŒ ๋งŒ๋“ค๋‹ค ๋ณด๋‹ˆ ๊ณ ์ณ์•ผํ•  ๋ถ€๋ถ„์ด ๊ฝค ์žˆ๋‹ค. ์ถ”ํ›„์— ๋“ค์–ด๊ฐˆ ์‹๋ฌผ ์ผ์ง€๊ฐ™์€ ๋‹ค์ด์–ด๋ฆฌ ๊ธฐ๋Šฅ๋„ ์ถ”๊ฐ€๋  ๋•Œ ๋ฆฌํŽ™ํ† ๋ง๋„ ํ•จ๊ป˜ ํ•ด์•ผ๊ฒ ๋‹ค.

๋ฒ„๊ทธ๋Š” ์žˆ๊ฒ ์ง€๋งŒ ์ž˜ ๋Œ์•„๊ฐ„๋‹ค ํ™€๋กค๋กœ

์‹œ๋“ค์‹œ๋“คํ–ˆ๋˜ ์‹๋ฌผ๋“ค์€ ์ž๋ฆฌ๋ฅผ ์˜ฎ๊ฒจ์ค€ ์ดํ›„๋กœ ์ƒํƒœ๊ฐ€ ์กฐ๊ธˆ์”ฉ ์ข‹์•„์ง€๊ณ  ์žˆ๋‹ค. ์ง‘์ด ์Šตํ•ด์„œ ์ธ์ง€ ๋•…์ด ์ž˜ ๋งˆ๋ฅด์ง€ ์•Š์•„์„œ ๊ฑฑ์ •ํ–ˆ๋Š”๋ฐ ์กฐ์‚ฌ๋ฅผ ํ•ด๋ณด๋‹ˆ ๋ฐ”๋žŒ์„ ์ฌ์–ด์ค˜์•ผ ํ•œ๋Œ€์„œ ์„ ํ’๊ธฐ๋ฅผ ํ•˜๋‚˜ ๋†”์คฌ๋‹ค.

๋‚ด ์„ ํ’๊ธฐ ....

๋ณ„๊ฑด ์•„๋‹ˆ์ง€๋งŒ ํœด๊ฐ€ ๋™์•ˆ์— ์˜ค๋žœ๋งŒ์— ์•ฑ ๋งŒ๋“ค์–ด์„œ ๋ธ”๋กœ๊ทธ๋ฅผ ์ ์–ด๋ดค๋‹ค.
๋‹ค์Œ์—๋Š” ๋งŒ๋“ ์ง€ ์ข€ ๋œ ๋ ˆํŠธ๋กœ ๋…ธํŠธ ๊ธฐ๋Šฅ ์—…๋ฐ์ดํŠธ ํ•˜๋ฉด์„œ ํฌ์ŠคํŒ… ๋˜ ํ•ด๋ด์•ผ๊ฒ ๋‹ค.

๐ŸŒฑ ์ด๋ฆ„์€ ์™œ Jobcho(์žก์ดˆ)์ธ๊ฐ€?

  • ๋‚จํŽธ์ด ๋ณ„ ์ƒ๊ฐ์—†์ด ์žก์ดˆ์ฒ˜๋Ÿผ ์ž˜ ์ž๋ž์œผ๋ฉด ์ข‹๊ฒ ๋‹ค๋Š” ๋งˆ์Œ์œผ๋กœ ์ง€์—ˆ๋‹ค๊ณ  ํ•ด์„ํ•ด๋ณธ๋‹ค.

๐ŸŒฟ ์•ฑ์Šคํ† ์–ด ๋งํฌ

๐Ÿ“ฑ Jobcho AppStore Link

profile
iOS Developer

0๊ฐœ์˜ ๋Œ“๊ธ€