๐Ÿ“ฉ ํ†ต์‹  ์‘๋‹ต์œผ๋กœ ์˜จ ๋ฐ์ดํ„ฐ๋ฅผ UI์— ๋ฐ˜์˜ํ•˜๋Š” ๋ฐฉ๋ฒ• ๊ณ ๋ฏผ๊ธ€

26755 ๋‹จ์–ด MVC๋น„๋™๊ธฐMVC

โ˜ ๏ธ ๋‚ด ์ฝ”๋“œ์˜ ๋ฌธ์ œ์ 

โฌ‡๏ธfirestore์˜ CRUD๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ๊ฐ์ฒด, ProjectFirestoreManager์˜ ํ†ต์‹ ์„ ํ†ตํ•ด Status์— ํ•ด๋‹นํ•˜๋Š” Project ๋ฅผ ์ฝ์–ด์˜ค๋Š” ๋ฉ”์„œ๋“œ

// ProjectFirestoreManager
 func read(of group: Status, completion: @escaping (Result<[[String : Any]], FirestoreError>) -> Void) {
            self.db.collection(FirestorePath.collection).whereField("status", isEqualTo: group.rawValue)
                .getDocuments() { (querySnapshot, err) in
                    if let err = err {
                        print("Error getting documents: \(err)")
                        completion(.failure(.doucmentNotExist))
                    } else {
                        var datas: [[String: Any]] = []
                        for document in querySnapshot!.documents {
                            print("\(document.documentID) => \(document.data())")
                            datas.append(document.data())
                        }
                        completion(.success(datas))
                    }
            }
    }

โฌ‡๏ธ ์•ฑ์˜ ํ•ต์‹ฌ ๋น„์ง€๋‹ˆ์Šค๋กœ์ง์„ ์ฒ˜๋ฆฌํ•˜๋Š” ProjectManager๊ฐ์ฒด
status์— ํ•ด๋‹นํ•˜๋Š” [Project]๋ฅผ ์ฝ์–ด์„œ ๋ฆฌํ„ดํ•˜๋Š” ๋ฉ”์„œ๋“œ
(๋ฐ์ดํ„ฐ์†Œ์Šค ๊ฐ์ฒด๋งŒ ๋ฐ”๊ฟ”์น˜๊ธฐํ•ด๋„ ๊ธฐ์กด ์ฝ”์–ด๋ฐ์ดํ„ฐ๋งค๋‹ˆ์ €์™€ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๋™์ผํ•˜๊ฒŒ ๊ฐ€์ ธ๊ฐ€๊ณ  ์‹ถ์—ˆ๋‹ค. ์ด๊ฒƒ์ด ๋ฌธ์ œ์˜ ์‹œ์ž‘...)

// ProjectManager
func readProject(of status: Status) -> [Project]? {
        var returnProjects: [Project]?
        // ํ†ต์‹  ์ฝ”๋“œ๋Š” ๋น„๋™๊ธฐ๋‹ค.
            projectDataManager.read(of: status) { result in
                switch result {
                case .success(let dicts):
                    let projects = dicts.compactMap { dict in
                        return Project(identifier: dict["identifier"] as? String,
                                              title: dict["title"] as? String,
                                              deadline: dict["deadline"] as? Date,
                                              description: dict["description"] as? String,
                                              status: dict["status"] as? Status)
                    }
                    returnProjects = projects
                case .failure(let error):
                    print(error.localizedDescription)
                    returnProjects = nil
                }
            }
        // ๐Ÿคท๐Ÿปโ€โ™€๏ธ ์‘๋‹ต์ด ์˜ค๊ธฐ์ „์— ๋ฆฌํ„ด๋จ. ๊ทธ๋ž˜์„œ ํ•ญ์ƒ nil ๋ฐ˜ํ™˜
        return returnProjects
    }

ํ†ต์‹ ์ด ๋„์ฐฉํ•˜๊ธฐ์ „์— ๋ฆฌํ„ดํ•ด์„œ ํ•ญ์ƒ nil์„ ๋‚ด๋ณด๋‚ธ๋‹ค.

// ๋ทฐ์ปจํŠธ๋กค๋Ÿฌ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ์–ด์„œ ๋ฐ˜์˜ํ•˜๋Š” ๋ถ€๋ถ„
 func applySnapshotToCell() {
        // ํ•ญ์ƒ nil์ด ๋“ค์–ด์˜จ๋‹ค.
        let projects = delegate?.readProject(of: projectStatus)
        
        var snapShot = NSDiffableDataSourceSnapshot<Section, Project>()
        snapShot.appendSections([.main])
        snapShot.appendItems(projectsToApply ?? [], toSection: .main)
        
        self.dataSource.apply(snapShot, animatingDifferences: true, completion: nil)
    }

๊ทธ๋ž˜์„œ ๋ทฐ์ปจ์—์„œ๋Š” ํ•ญ์ƒ nil์„ ๋ฐ›๋Š”๋‹ค.

๐Ÿ’ก ํ†ต์‹ ํ•ด์•ผ ๊ฐ’์„ ๋ฐ›์„ ์ˆ˜ ์žˆ๋Š” ์ฝ”๋“œ๋Š” ๊ฐ’์„ ๋ฆฌํ„ดํ•˜๋ ค๋ฉด ๋™๊ธฐ๋กœ ์ฒ˜๋ฆฌํ•ด์•ผํ•œ๋‹ค.

UI์—…๋ฐ์ดํŠธย ์ฝ”๋“œ๋„ย ๋ฆฌํ„ดํ•˜๋ฉดย ์•ˆ๋จ

ํ†ต์‹ +UI๊ฐ€ย ํ•˜๋‚˜์˜ย ํƒœ์Šคํฌ๋กœย ๋ฌถ์—ฌ์•ผํ•จ

(์ด๋ง์€ย ๋ชจ๋ธ์ด๋ž‘ย ๋ทฐ๊ฐ€ย ์ข…์†์„ฑ์ดย ์ƒ๊ธธย ์ˆ˜ย ๋ฐ–์—ย ์—†๋‹ค)


๐Ÿš€ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

1๏ธโƒฃย ๋ทฐ ์ปจํŠธ๋กค๋Ÿฌ์˜ย read ๋ฉ”์„œ๋“œ์—์„œ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœย completion์„ ๋ฐ›์•„์„œ ๋„˜๊ฒจ์ฃผ๋Š” ๋ฐฉ๋ฒ•. completion ์—๋Š” ๋ทฐ๋ฅผ ์—…๋ฐ์ดํŠธ ์‹œํ‚ค๋Š” ์ฝ”๋“œ๊ฐ€ ๋“ค์–ด๊ฐ

  • ๋กœ์ปฌ์˜ ๊ฒฝ์šฐ comlpetion์ด ํ•„์š” ์—†์Œ
  • ๋ฐ์ดํ„ฐ๊ฐ€ ์“ฐ์ด๋Š” ๊ณณ์—์„œ ์ ์ ˆํ•œ ์ฒ˜๋ฆฌ๋ฅผ ํ•  ์ˆ˜ ์žˆ์–ด์„œ ์ž์œจ์„ฑ์ด ๋†’์Œ
  • ๋‹จ์ 
    • ์ปจํŠธ๋กค๋Ÿฌ์— ๋ชจ๋ธ์˜ ๋กœ์ง์ด ์ฒจ๊ฐ€ ๋˜์„œ ์—ญํ• ๋ถ„๋ฆฌ๊ฐ€ ์–ด๋ ค์›€,,
    • ๋งŒ์•ฝ ๋ฐ›์€ ๋ฐ์ดํ„ฐ๊ฐ€ ์ปจํŠธ๋กค๋Ÿฌ์˜ ์—ฌ๊ธฐ์ €๊ธฐ์„œ ์‚ฌ์šฉ๋œ๋‹ค๋ฉด?
    • ๋ฐ์ดํ„ฐ ์ˆ˜์‹ ์— ๋”ฐ๋ฅธ ํ›„์† ์ž‘์—…์˜ ํƒ€์ด๋ฐ์„ ํ•ธ๋“ค๋งํ•ด์•ผํ•จ (ํƒ€์ด๋ฐ ๋งž์ถ”๊ธฐ ์–ด๋ ต)

2๏ธโƒฃย ProjectManager ๋ธ๋ฆฌ๊ฒŒ์ดํŠธ๋กœ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ• โœ…

  • ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›๋Š” ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ request๋กœ ์ƒˆ๋กœ ๋งŒ๋“ค๊ณ ,
  • ๋ฐ›์€ ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฉ”์„œ๋“œ๋ฅผ ๋ธ๋ฆฌ๊ฒŒ์ดํŠธ๋กœ ์ฒ˜๋ฆฌ.

3๏ธโƒฃย ํ†ต์‹ ์ด ๋„์ฐฉํ•˜๋ฉด ๋ทฐ์ปจ์œผ๋กœ ๋…ธํ‹ฐ๋ฅผ ๋‚ ๋ฆฌ๋Š” ๋ฐฉ๋ฒ• ๐Ÿ™…โ€โ™€๏ธ

  • ์ผ๋‹จ nil์„ ๋‚ด๋ณด๋‚ธ๋‹ค.
  • ๋…ธํ‹ฐํ”ผ์ผ€์ด์…˜ ์„ผํ„ฐ ์‚ฌ์šฉํ•ด์„œ ๋ฐ์ดํ„ฐ๊ฐ€ ์“ฐ์ด๋Š” ๊ณณ์— ๋…ธํ‹ฐ๋ฅผ ๋‚ ๋ฆฐ๋‹ค.
  • ๋‹จ์ 
    - ํ†ต์‹  ๊ฒฐ๊ณผ์— ๋Œ€ํ•œ ๋ถ€๋ถ„์„ ํ•˜๋‚˜ ํ•˜๋‚˜ ์ฑ™๊ฒจ๊ฐ€๋ฉด์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์—…๋ฐ์ดํŠธ ํ•ด์ฃผ์–ด์•ผํ•œ๋‹ค.โ˜ ๏ธ
    - ์ด๋ ‡๊ฒŒ ํ•  ๊ฒฝ์šฐ, ๊ด€๋ จ ์ฝ”๋“œ์— ๋Œ€ํ•œ ์‚ฌ์ด๋“œ ์ดํŽ™ํŠธ๊ฐ€ ์ƒ๊ธธ ์ˆ˜ ์žˆ๋‹ค.โ˜ ๏ธ

    ๋…ธํ‹ฐํ”ผ์ผ€์ด์…˜ ์„ผํ„ฐ๋Š” ์•ˆ ์“ด๋‹ค.


4๏ธโƒฃย ๋น„๋™๊ธฐ ์ฝ”๋“œ๋ฅผ ๋™๊ธฐ ์ฝ”๋“œ๋กœ ๋ฐ”๊ฟˆ

๊ฐ’์„ย ๋ฆฌํ„ดํ•˜๊ธฐย ์ „๊นŒ์ง€ย ๋ฆฌํ„ดํ•˜์ง€ย ๋ชปํ•˜๋„๋กย ๋ธ”๋ฝํ•˜๊ณ  (๋™๊ธฐ ํ๋ฆ„)
ํ”„๋กœ์ ํŠธ๋ฅผย ์ฝ์–ด์„œย UI์—…๋ฐ์ดํŠธย ํ•˜๋Š”ย ์ฝ”๋“œ๋ฅผย ๊ธ€๋กœ๋ฒŒ ํ์—ย ๋„ฃ๊ณ ย ๋น„๋™๊ธฐ๋กœย ์ฒ˜๋ฆฌ
(UI๋งŒย ๋ฉ”์ธํ์—์„œย ๋น„๋™๊ธฐ๋กœย ์ฒ˜๋ฆฌํ•˜๋„๋ก)

  • DispatchQueue.global().async โ†’ ์‹คํŒจ โŒ (๋น„๋™๊ธฐ๋ฅผ ๋น„๋™๊ธฐ์— ๋„ฃ๋Š” ๊ผด)
func readProject(of status: Status) -> [Project]? {
        var returnProjects: [Project]?
        DispatchQueue.global().sync {
           // ๋น„๋™๊ธฐ ๋ฉ”์„œ๋“œ๋Š” ๋ฐฑ๊ทธ๋ผ์šด๋“œํ๋กœ ์ผ์„ ๋ณด๋‚ด๋Š”๊ฒŒ ํ•  ์ผ์˜ ์ „๋ถ€ ์ด๋ฏ€๋กœ, 
           // ๊ธ€๋กœ๋ฒŒ ์‹ฑํฌ๋กœ ์ฒ˜๋ฆฌํ•ด์ฃผ์–ด๋„ ๋ฆฌํ„ดํ•ด๋ฒ„๋ฆฐ๋‹ค
            projectDataManager.read(of: status) { result in
                switch result {
                case .success(let dicts):
                    let projects = dicts.compactMap { dict in
                        return Project(identifier: dict["identifier"] as? String,
                                              title: dict["title"] as? String,
                                              deadline: dict["deadline"] as? Date,
                                              description: dict["description"] as? String,
                                              status: dict["status"] as? Status)
                    }
                    returnProjects = projects
                case .failure(let error):
                    // TODO: - ์˜คํ”„๋ผ์ธ์‹œ ์บ์‹œ ์‚ฌ์šฉํ•˜๋„๋ก ํ•ธ๋“ค๋ง
                    print(error.localizedDescription)
                    returnProjects = nil
                }
            }
        }
        return returnProjects
    }
  • DispatchGroup์„ ํ™œ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ• : ์Šค๋ ˆ๋“œ๋ฅผ ๋ง‰์•„์„œ ๋™๊ธฐ๋กœ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•
func readProject(of status: Status) -> [Project]? {
        var returnProjects: [Project]?
        let dispatchGroup = DispatchGroup()
        dispatchGroup.enter()
            cloudDataManager.read(of: status) { result in
                switch result {
                case .success(let dicts):
                    let projects = dicts.compactMap { dict in
                        return Project(identifier: dict["identifier"] as? String,
                                              title: dict["title"] as? String,
                                              deadline: dict["deadline"] as? Date,
                                              description: dict["description"] as? String,
                                              status: dict["status"] as? Status)
                    }
                    returnProjects = projects
                    dispatchGroup.leave()
                case .failure(let error):
                    // TODO: - ์˜คํ”„๋ผ์ธ์‹œ ์บ์‹œ ์‚ฌ์šฉํ•˜๋„๋ก ํ•ธ๋“ค๋ง
                    print(error.localizedDescription)
                    returnProjects = nil
                    dispatchGroup.leave()
                }
            }
         // โ›”๏ธ readProject๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ์Šค๋ ˆ๋“œ๊ฐ€ ์‘๋‹ต์ด ์˜ค๊ธฐ์ „๊นŒ์ง€ ๋ง‰ํž˜
        // ์Šค๋ ˆ๋“œ๋ฅผ ๋ง‰๋Š” ๊ฑด ์ข‹์€ ๋ฐฉํ–ฅ์€ ์•„๋‹˜
        dispatchGroup.wait()
        return returnProjects
    }

์œ„ ๋ฐฉ๋ฒ•์€ ์ •~๋ง ๋ฐฉ๋ฒ•์ด ์—†์„ ๋•Œ ํ•˜์ž.


๐Ÿ˜ถ ๋Š๋‚€์ 

๐Ÿ’ก ๋’ท๋ถย UI ์—…๋ฐ์ดํŠธ๋Š”ย ํ•ญ์ƒ,ย ์–ธ์ œ๋‚˜ย ์žˆ๋‹ค๊ณ ย ์ƒ๊ฐํ•˜๋ฉดย ๋ ย ๋“ฏ.

์ต์ˆ™ํ•ด์ง€์ž๐Ÿ˜ƒ

๋น„๋™๊ธฐ์˜ ๊ฒฐ๊ณผ๊ฐ’์„ ํ•ธ๋“ค๋งํ•ด์•ผํ•˜๋Š” ํ•จ์ˆ˜ ๊ฐ™์€ ๊ฒฝ์šฐ๋Š” ์–ด๋–ป๊ฒŒ ํ˜„๋ช…ํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ์ข‹์„๊นŒ?

์ปจํŠธ๋กค๋Ÿฌ๊ฐ€ ๋ชจ๋ธ์„ ๋ณด์œ ํ•ด์•ผํ•˜๊ธด ํ•˜์ง€๋งŒ,
๋ฐ์ดํ„ฐ ๋ณ€ํ™”์— ๋”ฐ๋ผ ์ผ๊ด„์ ์œผ๋กœ ๊ด€๋ จ ๋ฐ์ดํ„ฐ, ๋ทฐ ์—…๋ฐ์ดํŠธํ•˜๋Š”๊ฒŒ ๋‚ซ๊ฒ ๋‹ค.

๋‚ด ์ƒ๊ฐ์— ๋ฒ ์ŠคํŠธ๋Š”
๋ชจ๋ธ์˜ ํ๋ฆ„์€ ์ผ๊ด€์ ์ด๋ฉด ์ข‹๊ฒ ๋‹ค.
์ฆ‰, ์—ฐ๊ด€๋œ ๋ชจ๋ธ์€ ์ผ๋ฐฉ์ ์œผ๋กœ ๋‹ค ๊ฐ™์ด ๋ณ€ํ™”ํ•˜๋„๋ก.
๊ทธ๋ฆฌ๊ณ  ๋ชจ๋ธ์ด ๋ณ€ํ™”ํ•˜๋ฉด ์ž๋™์œผ๋กœ ๋ทฐ๊ฐ€ ์—…๋ฐ์ดํŠธ ๋  ์ˆ˜ ์žˆ๋„๋ก. ๋ชจ๋ธ์— observer๋ฅผ ๋“ฑ๋ก!

์ด ํŒจํ„ด์ด MVVM, ํด๋ฆฐ์•„ํ‚คํ…์ณ์ผ๊นŒ?

์ข‹์€ ์›นํŽ˜์ด์ง€ ์ฆ๊ฒจ์ฐพ๊ธฐ