Skip to content

๐Ÿง‘๐Ÿปโ€๐Ÿ’ป USW ๊ฐ•์˜ํ‰๊ฐ€ & ์‹œ๊ฐ„ํ‘œ ์„œ๋น„์Šค

Notifications You must be signed in to change notification settings

uswLectureEvaluation/SUWIKI-iOS

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

SUWIKI(2022.01 ~ )

๋ฆฌํŽ™ํ† ๋ง์€ SUWIKI ๋””๋ ‰ํ„ฐ๋ฆฌ์—์„œ ์ง„ํ–‰์ค‘์ž…๋‹ˆ๋‹ค.
App Store - SUWIKI



๋ฐ”๋กœ๊ฐ€๊ธฐ

๐Ÿง‘๐Ÿปโ€๐Ÿ’ป ์ˆ˜์›๋Œ€ํ•™๊ต ๊ณต์‹ ์‹œ๊ฐ„ํ‘œ & ๊ฐ•์˜ํ‰๊ฐ€ ์„œ๋น„์Šค

  • 3,600๋ช…์˜ ์‚ฌ์šฉ ์œ ์ €, ๋‹ค์šด๋กœ๋“œ ์•ฝ 6,700ํšŒ, ์—…๋ฐ์ดํŠธ 16ํšŒ
  • iOS 1์ธ ๊ฐœ๋ฐœ ๋ฐ ๋””์ž์ธ ์ง„ํ–‰, ๊ธฐํš ์ฐธ์—ฌ
  • ๋Œ€๋Ÿ‰ ๋ฐ์ดํ„ฐ ์ €์žฅ ์†๋„ ์•ฝ 3์ดˆ๋Œ€์—์„œ 0.6์ดˆ ๋ฏธ๋งŒ์œผ๋กœ ๊ฐœ์„ (์•ฝ 83%)
  • 1,000์ค„ ์ด์ƒ์˜ ์ŠคํŒŒ๊ฒŒํ‹ฐ ์ฝ”๋“œ ์•ฝ 240์ค„๋กœ ๊ฐœ์„ 
  • ์•„ํ‚คํ…์ฒ˜๊ฐ€ ์—†๋Š” ๊ตฌ์กฐ์—์„œ MVC, ํ˜„์žฌ๋Š” MVVM + Clean Architecture ์ ์šฉ ์ง„ํ–‰ ์ค‘
  • 2๋…„๊ฐ„ ์šด์˜ํ•˜๋ฉฐ ์•ฝ 12๋งŒ์ค„ ์ด์ƒ์˜ ์ฝ”๋“œ ์ž‘์„ฑ

UI

ํŽผ์ณ๋ณด๊ธฐ
์‹œ๊ฐ„ํ‘œ ๊ฐ•์˜ํ‰๊ฐ€ ์œ„์ ฏ
์‹œ๊ฐ„ํ‘œ ๊ฐ•์˜ํ‰๊ฐ€ ์œ„์ ฏ
์‹œ๊ฐ„ํ‘œ ์ถ”๊ฐ€ 1 ์‹œ๊ฐ„ํ‘œ ์ถ”๊ฐ€ 2 ์‹œ๊ฐ„ํ‘œ ์ถ”๊ฐ€ 3
์‹œ๊ฐ„ํ‘œ ์ถ”๊ฐ€ 1 ์‹œ๊ฐ„ํ‘œ ์ถ”๊ฐ€ 2 ์‹œ๊ฐ„ํ‘œ ์ถ”๊ฐ€ 3
๊ฐ•์˜ํ‰๊ฐ€ ์‹œํ—˜์ •๋ณด ๊ธ€ ์ž‘์„ฑ
๊ฐ•์˜ํ‰๊ฐ€ ์‹œํ—˜์ •๋ณด ๊ธ€ ์ž‘์„ฑ

๋‹ด๋‹น ๊ธฐ๋Šฅ

  • ์•ฑ ๊ตฌ์กฐ ์„ค๊ณ„ - Clean Architecture, MVVM
  • ์‹œ๊ฐ„ํ‘œ, ์‹œ๊ฐ„ํ‘œ ์œ„์ ฏ ๊ตฌํ˜„(Core Data, Firebase)
  • ๊ฐ•์˜ํ‰๊ฐ€ ๊ธฐ๋Šฅ ๊ตฌํ˜„(Alamofire, Network Layer ์„ค๊ณ„)
  • ํ† ํฐ ์ธํ„ฐ์…‰ํ„ฐ ๊ตฌํ˜„

์‚ฌ์šฉ ๊ธฐ์ˆ 

  • UIKit(Storyboard & Code Base), SwiftUI, SnapKit, Then, WidgetKit
  • Swift Concurrency, Actor, Combine
  • Core Data, Firebase
  • Alamofire, JWT, Clean Architecture, MVVM

๊ฐœ๋ฐœ์ผ์ง€ ๋ฐ ํŠธ๋Ÿฌ๋ธ” ์ŠˆํŒ…

1. ์‹œ๊ฐ„ํ‘œ UX ๊ฐœ์„ 

1.1 ์‹œ๊ฐ„ํ‘œ๋ฅผ ๊ฐ•์˜์— ์ถ”๊ฐ€ํ•˜๊ธฐ ์–ด๋ ค์šด UX














  • ์œ ์ €๋Š” ๊ฐ•์˜๋ช…์„ ๋ชจ๋ฅผ ๊ฒฝ์šฐ ๊ฐ•์˜๋ฅผ ์ถ”๊ฐ€ํ•˜๊ธฐ ์–ด๋ ค์šด UX ๋ฐœ์ƒ

ํ•ด๊ฒฐ๋ฐฉ์•ˆ

  • ํ•™๊ณผ๋ฅผ ์šฐ์„  ์„ ํƒํ•  ์ˆ˜ ์žˆ๋„๋ก UI ์ˆ˜์ •, ์œ ์ €์˜ ํ•™๊ณผ ์ฆ๊ฒจ์ฐพ๊ธฐ ๊ธฐ๋Šฅ ์ถ”๊ฐ€
  • ๊ฐ•์˜, ๊ต์ˆ˜๋ช… ๊ฒ€์ƒ‰์œผ๋กœ ๊ฐ•์˜ ๊ฒ€์ƒ‰ ๊ฐ€๋Šฅ. ๋„์–ด์“ฐ๊ธฐ ๊ณ ๋ คํ•œ ๋ฐ์ดํ„ฐ ํ•„ํ„ฐ๋ง ๊ธฐ๋Šฅ ๊ตฌํ˜„

1.2 HIG์— ์ ํ•ฉํ•˜์ง€ ์•Š์€ UI/UX

  • iOS์˜ UX์™€๋Š” ๋™๋–จ์–ด์ง„ UI / UX, ๊ฐ•์˜ ์ •๋ณด ์ˆ˜์ •์˜ ๋ถˆํ•„์š”ํ•œ UX ๋ฐœ์ƒ

ํ•ด๊ฒฐ๋ฐฉ์•ˆ

  • ์• ํ”Œ์˜ UX์™€ ์œ ์‚ฌํ•œ ํ˜•ํƒœ๋กœ UI ์ˆ˜์ •
  • ๊ฐ•์˜ ์ •๋ณด ์ˆ˜์ • ๊ธฐ๋Šฅ ์‚ญ์ œ

2. ๋ฆฌํŽ™ํ† ๋ง ๋ฐ ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ ์„ค๊ณ„

์ด๋ฏธ์ง€

๊ธฐ์กด SUWIKI ์ฝ”๋“œ์˜ ๋ฌธ์ œ

  • ์Šคํ† ๋ฆฌ๋ณด๋“œ ์œ„์ฃผ์˜ UI ๊ตฌํ˜„
  • ๋ณ„๋„์˜ ์•„ํ‚คํ…์ฒ˜ ํŒจํ„ด์ด ์ ์šฉ๋˜์ง€ ์•Š์Œ
  • ํ•œ ํŒŒ์ผ์˜ ์ฝ”๋“œ๊ฐ€ 1์ฒœ์ค„์ด ๋„˜์–ด๊ฐ€๊ณ , ๋น„์Šทํ•œ ๊ธฐ๋Šฅ์˜ ํ•จ์ˆ˜๊ฐ€ ์—ฌ๋Ÿฌ๊ฐœ ์ •์˜๋จ
  • ์ฝ”๋“œ๊ฐ„์˜ ๊ฒฐํ•ฉ๋„๊ฐ€ ๋†’๊ณ  ์‘์ง‘๋„๊ฐ€ ๋‚ฎ์•„ ์žฌ์‚ฌ์šฉ์„ฑ์ด ๋–จ์–ด์ง

ํ˜„์žฌ

  • UI๋Š” SwiftUI์™€ UIKit(Code Base)์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ตฌํ˜„ ์ค‘
  • ๋ฐ์ดํ„ฐ ์ €์žฅ ๊ณต๊ฐ„์„ Realm์—์„œ Core Data๋กœ ์ „ํ™˜
  • MVC ํŒจํ„ด์„ ์ ์šฉํ–ˆ์—ˆ์œผ๋‚˜, ViewController์˜ ์ฝ”๋“œ๊ฐ€ ๋งค์šฐ ๊ธธ์–ด์ง€๋Š” ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ณ ์ž MVVM์„ ์ ์šฉ
  • SwiftUI์™€ UIKit์„ MVVM ํŒจํ„ด ๊ธฐ๋ฐ˜์œผ๋กœ ๊ตฌํ˜„ ์ง„ํ–‰ ์ค‘
  • ๋ฐ์ดํ„ฐ ์ €์žฅ ๊ณต๊ฐ„์˜ ์ „ํ™˜๊ณผ ๋‹ค๋ฅธ UI ํ”„๋ ˆ์ž„์›Œํฌ์— ๋Œ€์‘ํ•˜๊ณ ์ž ํด๋ฆฐ ์•„ํ‚คํ…์ฒ˜๋ฅผ ์ ์šฉํ•˜์—ฌ ๋Œ€์‘์ค‘

3. App Group ์ ์šฉ

์ด๋ฏธ์ง€

App Group ์ ์šฉ ์ด์œ 

  • ๊ทธ๋ฆผ๊ณผ ๊ฐ™์ด, Core Data์— ์ €์žฅ๋˜๋Š” ๋ฐ์ดํ„ฐ๋ฅผ App๊ณผ Widget Extension์—์„œ ๊ณต์œ ๋˜์–ด์•ผ ํ•จ
  • ๊ธฐ์กด ๋ฐฉ์‹์˜ Core Data๋Š” App ๋‚ด๋ถ€์—์„œ๋งŒ ์ ‘๊ทผ์ด ๊ฐ€๋Šฅ

์ ์šฉ ๋ฐฉ์‹

  • Core Data์— ์ €์žฅ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ์œ„์ ฏ์—์„œ๋„ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์Œ
  • ์„ ํƒ๋œ ์‹œ๊ฐ„ํ‘œ์˜ ID๋ฅผ ์ถ”์ ํ•˜๊ธฐ ์œ„ํ•ด UserDefaults ๋˜ํ•œ App Group์„ ์ ์šฉํ•จ

4. ์ฝ”์–ด๋ฐ์ดํ„ฐ NSBatchInsertRequest๋ฅผ ์‚ฌ์šฉํ•œ ์„ฑ๋Šฅ ๊ฐœ์„ 

func saveFirebaseCourse(course: [[String: Any]]) throws {
    try deleteFirebaseCourse()
    guard let entity = NSEntityDescription.entity(forEntityName: "FirebaseCourse", in: context) else {
        throw CoreDataError.entityError
    }
    let batchInsertRequest = NSBatchInsertRequest(entity: entity, objects: course)
    if let fetchResult = try? context.execute(batchInsertRequest),
        let batchInsertResult = fetchResult as? NSBatchInsertResult,
        let success = batchInsertResult.result as? Bool, 
        success {
            return
        }
    print(CoreDataError.batchInsertError.localizedDescription)
}

๋ฐ์ดํ„ฐ ์ €์žฅ ์‹œ ๋ฐœ์ƒํ•˜๋Š” ๋ฌธ์ œ ์ •์˜

  • ๋งค ํ•™๊ธฐ๋งˆ๋‹ค Firebase์— ์ €์žฅ๋œ 2,000์—ฌ ๊ฐœ์˜ ๋ฐ์ดํ„ฐ๋ฅผ Core Data์— ์ €์žฅํ•ด์•ผํ•จ
  • ๊ธฐ์กด์˜ ๋ฐฉ์‹์€ ๋ฐ์ดํ„ฐ์˜ ๊ฐฏ์ˆ˜๋งŒํผ ๋ฐ˜๋ณตํ•˜์—ฌ ์ €์žฅํ•˜์˜€์œผ๋‚˜ ๋ฌธ์ œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Œ
  1. ๋ฐ์ดํ„ฐ ์ €์žฅ ์‹œ ์•ฝ 3์ดˆ ์†Œ์š”
  2. ๊ฐ„ํ—์ ์œผ๋กœ ์—๋Ÿฌ ๋ฐœ์ƒ(Cocoa 132001). ๋Œ€๋Ÿ‰์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ˜๋ณต๋ฌธ์„ ํ†ตํ•ด ์ฒ˜๋ฆฌํ•˜๋Š” ๊ณผ์ •์—์„œ context์™€ entity๋ฅผ ๊ณ„์† ์ ‘๊ทผํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค๊ณ  ์ถ”๋ก 

๋”ฐ๋ผ์„œ NSBatchInsertRequest๋ฅผ ์ ์šฉํ•˜์—ฌ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐ

  • ๋ฐ์ดํ„ฐ ์ €์žฅ์— ์•ฝ 0.5์ดˆ ์†Œ์š”
  • ๋‹จ์ผ ํ˜ธ์ถœ๋งŒ ์Šคํƒ ํ”„๋ ˆ์ž„์— ์˜ฌ๋ผ๊ฐ€ ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฅผ ์˜๊ตฌ ์ €์žฅ์†Œ์— ์‚ฝ์ž…ํ•  ์ˆ˜ ์žˆ๊ธฐ์— ๋ฉ”๋ชจ๋ฆฌ ํšจ์œจ์„ฑ ์ฆ๊ฐ€

5. Alamofire serializingDecodable ์ ์šฉ

class APIProvider {
    static func request<T: Decodable>(
        _ object: T.Type,
        target: TargetType
    ) async throws -> T {
        return try await AlamofireManager
            .shared
            .session
            .request(target)
            .serializingDecodable()
            .value
    }
}
/// func search: ๊ฐ•์˜ํ‰๊ฐ€๋ฅผ ๊ฒ€์ƒ‰ํ•œ ํ›„, ๊ฒ€์ƒ‰ ๋ฐ์ดํ„ฐ๋ฅผ ์„œ๋ฒ„์—์„œ ๋‚ด๋ ค๋ฐ›์Šต๋‹ˆ๋‹ค.
/// ๋ฌดํ•œ์Šคํฌ๋กค ๊ธฐ๋Šฅ์„ ์œ„ํ•ด ํŽ˜์ด์ง€๊ฐ€ 1์ผ ๊ฒฝ์šฐ search ๋ฐ์ดํ„ฐ๋กœ ์ดˆ๊ธฐํ™”, ์•„๋‹ ๊ฒฝ์šฐ append ํ•ฉ๋‹ˆ๋‹ค.
func search() async throws {
    guard !searchText.isEmpty else { return }
    do {
        if self.searchPage == 1 {
            searchLecture = try await searchUseCase.excute(searchText: searchText,
                                                           option: option,
                                                           page: searchPage,
                                                           major: major)
            await MainActor.run {
                lecture = searchLecture
            }
        } else {
            let searchData = try await searchUseCase.excute(searchText: searchText,
                                                            option: option,
                                                            page: searchPage,
                                                            major: major)
            await MainActor.run {
                searchLecture.append(contentsOf: searchData)
                lecture.append(contentsOf: searchData)
            }
        }
    } catch {
        fatalError(error.localizedDescription)
    }
}

responseDecodable๊ณผ serializingDecodable์˜ ์ฐจ์ด

  • ํ˜„์žฌ ์ฝ”๋“œ ์ž‘์„ฑ ์‹œ completion handler๋ฅผ ์ง€์–‘ํ•˜๊ณ  async / await๋ฅผ ์ง€ํ–ฅํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑ ์ค‘
  • responseDecodable ๋Œ€์‹  serializingDecodable์„ ์‚ฌ์šฉ(serializingDecodable์€ DataTask ๋ฐ˜ํ™˜)
  • responseDecodable์€ ํ•ธ๋“ค๋Ÿฌ๊ฐ€ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ์—์„œ ํ˜ธ์ถœ๋˜์ง€๋งŒ, serializngDecodable์€ ์Šค๋ ˆ๋“œ ๊ด€๋ฆฌ๊ฐ€ ๋”ฐ๋กœ ๋˜์ง€ ์•Š์Œ(responseDecodable์€ ๋‚ด๋ถ€์ ์œผ๋กœ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ ๊ธฐ๋Šฅ ์‹คํ–‰ ์œ„์น˜๊ฐ€ ๋ฉ”์ธํ๋กœ ์ง€์ •๋˜์–ด ์žˆ์Œ)

ํ•ด๊ฒฐ ๋ฐฉ์•ˆ

  • ํ˜ธ์ถœ๋ถ€ ๋ฉ”์†Œ๋“œ ์ „์ฒด๊ฐ€ ์•„๋‹Œ ๋ฐ˜ํ™˜๋œ ๋ฐ์ดํ„ฐ์— Main Actor ์ ์šฉํ•˜์—ฌ ํ”„๋กœํผํ‹ฐ ์—…๋ฐ์ดํŠธ

6. Network Layer ์„ค๊ณ„

๊ธฐ์กด ๋„คํŠธ์›Œํฌ ํ˜ธ์ถœ ๋ฐฉ์‹์˜ ๋ฌธ์ œ

func getDetailPage(){
    let url = "https://api.kr"

    let headers: HTTPHeaders = [
        "Authorization" : String(keychain.get("AccessToken") ?? "")
    ]

    AF.request(url, method: .get, encoding: URLEncoding.default, headers: headers, interceptor: BaseInterceptor()).validate().responseJSON { (response) in
        
        let data = response.value
        let json = JSON(data ?? "")["data"]
        
        let totalAvg = String(format: "%.1f", round(json["lectureTotalAvg"].floatValue * 1000) / 1000)
        let totalSatisfactionAvg = String(format: "%.1f", round(json["lectureSatisfactionAvg"].floatValue * 1000) / 1000)
        let totalHoneyAvg = String(format: "%.1f", round(json["lectureHoneyAvg"].floatValue * 1000) / 1000)
        let totalLearningAvg = String(format: "%.1f", round(json["lectureLearningAvg"].floatValue * 1000) / 1000)
        
        let detailLectureData = detailLecture(id: json["id"].intValue, semesterList: json["semesterList"].stringValue, professor: json["professor"].stringValue, majorType: json["majorType"].stringValue, lectureType: json["lectureType"].stringValue, lectureName: json["lectureName"].stringValue, lectureTotalAvg: totalAvg, lectureSatisfactionAvg: totalSatisfactionAvg, lectureHoneyAvg: totalHoneyAvg, lectureLearningAvg: totalLearningAvg, lectureTeamAvg: json["lectureTeamAvg"].floatValue, lectureDifficultyAvg: json["lectureDifficultyAvg"].floatValue, lectureHomeworkAvg: json["lectureHomeworkAvg"].floatValue)
        
        self.detailLectureArray.append(detailLectureData)
        self.lectureViewUpdate()
    }
}
  • ๋™์ผํ•˜๊ฒŒ ์ž‘๋™ํ•˜๋Š” ๋ฉ”์†Œ๋“œ๋ฅผ ๋งค๋ฒˆ ์ž‘์„ฑํ•˜๋Š” ๋ฌธ์ œ ๋ฐœ์ƒ
  • DTO๋ฅผ ๋”ฐ๋กœ ์ •์˜ํ•˜์ง€ ์•Š๊ณ , SwiftyJSON์„ ์‚ฌ์šฉํ•ด์„œ ๋งค๋ฒˆ ํ•˜๋‚˜์”ฉ ํŒŒ์‹ฑํ•จ
  • ์ฝ”๋“œ์˜ ์žฌ์‚ฌ์šฉ์„ฑ, ๊ฐ€๋…์„ฑ์ด ๋งค์šฐ ๋–จ์–ด์ง
  • ๋ฉ”์†Œ๋“œ์—์„œ URL์ด ๋…ธ์ถœ๋จ

๊ฐœ์„  ํ›„

extension DTO {
    struct DetailLectureResponse: Codable {
        /// ๊ฐ•์˜ ID
        let id: Int
        /// ํ•ด๋‹น ๊ฐ•์˜ ๋…„๋„ + ํ•™๊ธฐ -> "2021-1,2022-1"
        let semesterList: String
        /// ๊ต์ˆ˜๋ช…
        let professor: String
        /// ๊ฐœ์„ค ํ•™๊ณผ
        let majorType: String
        /// ์ด์ˆ˜ ๊ตฌ๋ถ„
        let lectureType: String
        /// ๊ฐ•์˜๋ช…
        let lectureName: String
        /// ๊ฐ•์˜ํ‰๊ฐ€ ํ‰๊ท  ์ง€์ˆ˜
        let lectureTotalAvg: Float
        /// ๊ฐ•์˜ํ‰๊ฐ€ ๋งŒ์กฑ๋„ ์ง€์ˆ˜
        let lectureSatisfactionAvg: Float
        /// ๊ฐ•์˜ํ‰๊ฐ€ ๊ฟ€๊ฐ• ์ง€์ˆ˜
        let lectureHoneyAvg: Float
        /// ๊ฐ•์˜ํ‰๊ฐ€ ๋ฐฐ์›€ ์ง€์ˆ˜
        let lectureLearningAvg: Float
        /// ๊ฐ•์˜ํ‰๊ฐ€ ํŒ€ํ”Œ ์ง€์ˆ˜
        let lectureTeamAvg: Float
        /// ๊ฐ•์˜ํ‰๊ฐ€ ๋‚œ์ด๋„ ์ง€์ˆ˜
        let lectureDifficultyAvg: Float
        /// ๊ฐ•์˜ํ‰๊ฐ€ ๊ณผ์ œ ์ง€์ˆ˜
        let lectureHomeworkAvg: Float
    }
}
extension APITarget.Lecture {
    var targetURL: URL {
        URL(string: APITarget.baseURL + "lecture")!
    }

    var method: Alamofire.HTTPMethod {
        switch self {
        case .getHome:
            return .get
        case .search:
            return .get
        case .detail:
            return .get
        }
    }

    var path: String {
        switch self {
        case .getHome:
            "/all"
        case .search:
            "/search"
        case .detail:
            ""
        }
    }

    var parameters: RequestParameter {
        switch self {
        case let .getHome(allLectureRequest):
            return .query(allLectureRequest)
        case let .search(searchLectureRequest):
            return .query(searchLectureRequest)
        case let .detail(detailLectureRequest):
            return .query(detailLectureRequest)
        }
    }
}
func fetchDetail(
    id: Int
) async throws -> DetailLecture {
    let target = APITarget.Lecture.detail(
        DTO.DetailLectureRequest(lectureId: id)
    )
    let dtoDetailLecture = try await APIProvider.request(
        DTO.DecodingDetailLectureResponse.self,
        target: target
    )
    return dtoDetailLecture.detailLecture.entity
}
  • ๋„คํŠธ์›Œํฌ ๊ณ„์ธต ์„ค๊ณ„
  • ์ถ”์ƒํ™”๋ฅผ ํ†ตํ•œ ๊ณตํ†ต๋œ ๊ธฐ๋Šฅ๋“ค์„ ์ธํ„ฐํŽ˜์ด์Šค๋กœ ์ •์˜, ์ค‘๋ณต ๊ธฐ๋Šฅ์€ ํ™•์žฅ์œผ๋กœ ๊ตฌํ˜„
  • ํ™•์žฅ์„ฑ๊ณผ ์žฌ์‚ฌ์šฉ์„ฑ์„ ๊ณ ๋ คํ•œ DTO ์„ค๊ณ„
  • ํด๋ฆฐ์•„ํ‚คํ…์ฒ˜์˜ ๋ฐ์ดํ„ฐ ํ๋ฆ„์„ ์ง€ํ–ฅ

7. ์ŠคํŒŒ๊ฒŒํ‹ฐ ์ฝ”๋“œ ๋ฆฌํŽ™ํ† ๋ง

๋ฌธ์ œ ์ƒํ™ฉ

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

ํ•ด๊ฒฐ ๋ฐฉ์•ˆ

  • ์‹œ๊ฐ„ํ‘œ ์ค‘๋ณต ๊ฒ€์ฆ ํด๋ž˜์Šค ์ƒ์„ฑ
  • ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋ฉ”์†Œ๋“œ ์‚ฌ์šฉ, ์ค‘๋ณต ์ฝ”๋“œ ์ตœ์†Œํ™”๋กœ ๊ฐ€๋…์„ฑ ์ฆ๊ฐ€
  • 2์ฒœ์—ฌ๊ฐœ์˜ ์‹œ๊ฐ„ํ‘œ ๋ฐ์ดํ„ฐ์˜ ์ผ€์ด์Šค ์ •์˜, ์ผ€์ด์Šค ๋ณ„๋กœ ์‹œ๊ฐ„ํ‘œ ์ค‘๋ณต ๊ฒ€์ฆ ๋กœ์ง์„ ๋Œ€์‘ํ•จ
    (์ผ๋ฐ˜ ๊ฐ•์˜ - ํ•˜๋‚˜์˜ ๊ฐ•์˜์™€ ํ•˜๋‚˜์˜ ๊ฐ•์˜์‹ค, ๊ฐ•์˜ ์‹œ๊ฐ„ 1 : ๊ฐ•์˜์‹ค N, ๊ฐ•์˜์‹ค 1 : ๊ฐ•์˜ ์‹œ๊ฐ„ N, ์˜จ๋ผ์ธ ๊ฐ•์˜)
  • ์•ฝ 240์ค„๋กœ ๊ฐœ์„ 

8. Core Data thread-safe ๋ฌธ์ œ

  • ํ•ด๊ฒฐํ•ด๋ณด๊ธฐ!

About

๐Ÿง‘๐Ÿปโ€๐Ÿ’ป USW ๊ฐ•์˜ํ‰๊ฐ€ & ์‹œ๊ฐ„ํ‘œ ์„œ๋น„์Šค

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages