포스트

단어장 프로젝트 (9)

예외처리

현재 코드들이 코어데이터에 값이 있는것을 기반으로 하다보니 아무것도 없는 상태에서 처리를 하는 예외처리가 필요하다.

우선 코어데이터를 가져와서 단어장의 이름이 있는지를 확인

1
2
3
4
5
6
7
8
9
10
checkData()
            if data.filter({ $0.bookCaseName!.count != 0 }).count == 0 { // added
                let alert = alertController.makeNormalAlert(title: "데이터가 없습니다.", message: "단어장과 단어를 생성해주세요\n게임을 진행하려면 한 단어장에 최소 4개의 단어가 필요합니다.")
                self.present(alert, animated: true)
            } else {
                let vc = SelectVocaViewController()
                vc.modalPresentationStyle = .custom
                vc.transitioningDelegate = self
                self.present(vc, animated: true, completion: nil)
            }

May-22-2024 12-00-50

그리고 단어장이 있는 상태에서 설정하기를 누를때 마지막으로 한번더 체크

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func setUpGame () {
        guard let currentVC = currentViewController as? SelectVocaViewController else { return }
        let count = currentVC.quizCount
        let category = currentVC.selectedCategory
        if currentVC.checkDataCount(query: category) < 4 {
            let alert = currentVC.alertController.makeNormalAlert(title: "갯수 부족", message: "한 단어장에 최소 4개의 단어가 있어야합니다.")
            currentVC.present(alert, animated: true)
        } else {
            let data = GenQuizModel(category: category, quizCount: count)
            NotificationCenter.default.post(name: .sender, object: data)
            currentVC.dismiss(animated: true)
        }
        
    }

May-22-2024 11-52-01

시작할때 데이터를 못받는 문제가 있어서 위치 조정

1
2
3
4
5
6
7
8
9
10
11
12
13
func setUpGame () {
        guard let currentVC = currentViewController as? SelectVocaViewController else { return }
        let count = currentVC.quizCount
        let category = currentVC.selectedCategory
        let data = GenQuizModel(category: category, quizCount: count) // moved
        if currentVC.checkDataCount(query: category) < 4 {
            let alert = currentVC.alertController.makeNormalAlert(title: "갯수 부족", message: "한 단어장에 최소 4개의 단어가 있어야합니다.")
            currentVC.present(alert, animated: true)
        } else {
            NotificationCenter.default.post(name: .sender, object: data)
            currentVC.dismiss(animated: true)
        }
    }

May-22-2024 11-56-25

완료.

CloudKit 사용

CleanShot 2024-05-22 at 15 52 56@2x

여기서 추가.

CleanShot 2024-05-22 at 15 55 59@2x

그다음 cloudkit을 체크하고

아래 + 버튼을 클릭하여 identifier이름의 컨테이너를 추가해본다.

CleanShot 2024-05-22 at 17 18 50@2x

cloudkit console을 들어가면 추가한 identifier에 해당하는 내용이 보인다.

여기까지 세팅은 여기를 참고했다.

물론 빠졌지만 background도 해주었다.

Background modes - Remote Notifications를 체크하는 이유는 새로운 컨텐츠가 생겼을 때, 어떠한 알림 없이 조용하게 알리기 위해 사용한다.

여기서 부터는 여기를 참고한다. 필요한 Docs를 별도로 정리해 줘서 그걸 보면서 만들 수 있을 듯 하다.

그리고 우리가 만든 코어데이터로 가서

CleanShot 2024-05-22 at 17 36 28@2x

여기를 체크해주었다.

Docs를 보니 container를 바꿔주어야 한다고 한다.

1. Appdelegate 변경

심플하다 NSPersistentContainer 이녀석을 NSPersistentCloudKitContainer 이녀석으로 바꿔준다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
lazy var persistentContainer: NSPersistentCloudKitContainer = { // modified
        /*
         The persistent container for the application. This implementation
         creates and returns a container, having loaded the store for the
         application to it. This property is optional since there are legitimate
         error conditions that could cause the creation of the store to fail.
        */
        let container = NSPersistentCloudKitContainer(name: "Vocabulary") // modified
        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
            if let error = error as NSError? {
                fatalError("Unresolved error \(error), \(error.userInfo)")
            }
        })
        return container
    }()swift

아래 보니 configure가 나는 하나라서 크게 의미가 없을 듯

저건 여러 configure에 따른 세팅이라 지금은 추가 안해도 될듯하다.

2. Cloudkit을 위한 CoreData Model 생성

어찌 저찌 하다가

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
func saveCloud() {
            // 모든 엔터티에 대한 fetch 요청 생성
            let entityNames = managedContext!.persistentStoreCoordinator?.managedObjectModel.entities.map { $0.name } ?? []
            
            let database = CKContainer(identifier: "iCloud.com.teamproject.Vocabularytest")
            
            for entityName in entityNames {
                let request: NSFetchRequest<WordEntity> = WordEntity.fetchRequest()
                let predicate = NSPredicate(format: "bookCaseName == %@", entityName!)
                request.predicate = predicate
                
                do {
                    let items = try managedContext!.fetch(request)
                    for item in items {
                        
                        
                    }
                } catch {
                    print("Failed to fetch items for entity \(entityName!): \(error)")
                }
                
                do {
                    try managedContext!.save()
                } catch {
                    print("Failed to save context: \(error)")
                }
            }
    }

저장이 되질 않아 변경

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
func saveCloud() {
        // 모든 엔터티에 대한 fetch 요청 생성
        let entityNames = managedContext!.persistentStoreCoordinator?.managedObjectModel.entities.map { $0.name } ?? []
        
        let database = CKContainer(identifier: "iCloud.com.teamproject.Vocabularytest").publicCloudDatabase
        
        managedContext?.automaticallyMergesChangesFromParent = true
        
        for entityName in entityNames {
            let request: NSFetchRequest<WordEntity> = WordEntity.fetchRequest()
            let predicate = NSPredicate(format: "bookCaseName == %@", entityName!)
            let record = CKRecord(recordType: entityName!)
            request.predicate = predicate
            do {
                let items = try managedContext!.fetch(request)
                for item in items {
                    record.setValue(item.antonym, forKey: "antonym")
                    record.setValue(item.bookCaseName, forKey: "bookCaseName")
                    record.setValue(item.date, forKey: "date")
                    record.setValue(item.definition, forKey: "definition")
                    record.setValue(item.detail, forKey: "detail")
                    record.setValue(item.memory, forKey: "memory")
                    record.setValue(item.pronunciation, forKey: "pronunciation")
                    record.setValue(item.synonym, forKey: "synonym")
                    record.setValue(item.word, forKey: "word")
                    
                    database.save(record) { record, error in
                        print("saved")
                    }
                    
                }
            } catch {
                print("Failed to fetch items for entity \(entityName!): \(error)")
            }
            
            do {
                try managedContext!.save()
            } catch {
                print("Failed to save context: \(error)")
            }
        }
    }

하지만 그래도 저장이 되질 않는다.

혹시나 싶어 request의 결과의 갯수를 확인해보니 0이나온다.

즉 애초에 가져오는것도 안되었던것.

GPT사용

결국 gpt에 의존한다. 이것도 여러차례 질문하며 내나름대로 코드도 수정하고 그걸 다시 적용하고 안되면

검색해서 좀 찾아보고 그러다 자료가없으면 물어보고를 몇시간 했는지 모르겠다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
func syncData() {
        syncEntity(BookCase.self, recordType: "BookCase")
        syncEntity(WordEntity.self, recordType: "WordEntity")
    }
    
func syncEntity<T: NSManagedObject>(_ entityType: T.Type, recordType: String) {
        let fetchRequest = T.fetchRequest()
        
        do {
            let results = try managedContext!.fetch(fetchRequest)
            for object in results {
                saveToCloudKit(object as! NSManagedObject, recordType: recordType)
            }
        } catch {
            print("Failed to fetch data from Core Data: \(error)")
        }
    }
    
func saveToCloudKit(_ object: NSManagedObject, recordType: String) {
        let database = CKContainer(identifier: "iCloud.com.teamproject.Vocabularytest").publicCloudDatabase
        let record = CKRecord(recordType: recordType)
        
        // Set record fields based on entity type
        if let bookCase = object as? BookCase {
            record["name"] = bookCase.name as CKRecordValue?
            record["explain"] = bookCase.explain as CKRecordValue?
            record["meaning"] = bookCase.meaning as CKRecordValue?
            record["image"] = bookCase.image as CKRecordValue?
            record["word"] = bookCase.word as CKRecordValue?
            
        } else if let wordEntity = object as? WordEntity {
            record["antonym"] = wordEntity.antonym as CKRecordValue?
            record["bookCaseName"] = wordEntity.bookCaseName as CKRecordValue?
            record["date"] = wordEntity.date as CKRecordValue?
            record["definition"] = wordEntity.definition as CKRecordValue?
            record["detail"] = wordEntity.detail as CKRecordValue?
            record["memory"] = wordEntity.memory as CKRecordValue?
            record["pronunciation"] = wordEntity.pronunciation as CKRecordValue?
            record["synonym"] = wordEntity.synonym as CKRecordValue?
            record["word"] = wordEntity.word as CKRecordValue?
        }
        
        database.save(record) { record, error in
            if let error = error {
                print("Error saving record to CloudKit: \(error)")
            } else {
                print("\(recordType) saved to CloudKit successfully")
            }
        }
    }

Gpt가 제안한거에서 조금 수정을 했다.

하지만 중복문제가 발생

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
func syncData() {
        syncEntity(BookCase.self, recordType: "BookCase")
        syncEntity(WordEntity.self, recordType: "WordEntity")
    }
    
func syncEntity<T: NSManagedObject>(_ entityType: T.Type, recordType: String) {
        let fetchRequest = T.fetchRequest()
        
        do {
            let results = try managedContext!.fetch(fetchRequest)
            for object in results {
                // Ensure the object has a UUID
                if let bookCase = object as? BookCase, bookCase.uuid == nil {
                    bookCase.uuid = UUID().uuidString
                } else if let wordEntity = object as? WordEntity, wordEntity.uuid == nil {
                    wordEntity.uuid = UUID().uuidString
                }
                saveToCloudKit(object as! NSManagedObject, recordType: recordType)
            }
        } catch {
            print("Failed to fetch data from Core Data: \(error)")
        }
    }
    
func saveToCloudKit(_ object: NSManagedObject, recordType: String) {
        let database = CKContainer(identifier: "iCloud.com.teamproject.Vocabularytest").privateCloudDatabase
        var recordID: CKRecord.ID
        
        // Generate a unique identifier for the record
        if let bookCase = object as? BookCase {
            recordID = CKRecord.ID(recordName: bookCase.uuid ?? UUID().uuidString)
        } else if let wordEntity = object as? WordEntity {
            recordID = CKRecord.ID(recordName: wordEntity.uuid ?? UUID().uuidString)
        } else {
            print("Unknown object type")
            return
        }
        
        // Check if the record already exists
        database.fetch(withRecordID: recordID) { fetchedRecord, error in
            if let fetchedRecord = fetchedRecord {
                // Record already exists, update it if necessary
                self.updateRecord(fetchedRecord, withObject: object, recordType: recordType, database: database)
            } else if let ckError = error as? CKError, ckError.code == .unknownItem {
                // Record does not exist, create a new one
                let newRecord = CKRecord(recordType: recordType, recordID: recordID)
                self.populateRecord(newRecord, withObject: object, recordType: recordType)
                self.saveRecord(newRecord, toDatabase: database)
            } else {
                print("Error fetching record from CloudKit: \(error?.localizedDescription ?? "Unknown error")")
            }
        }
    }
    
func updateRecord(_ record: CKRecord, withObject object: NSManagedObject, recordType: String, database: CKDatabase) {
        populateRecord(record, withObject: object, recordType: recordType)
        saveRecord(record, toDatabase: database)
    }
    
func populateRecord(_ record: CKRecord, withObject object: NSManagedObject, recordType: String) {
        // Set record fields based on entity type
        if let bookCase = object as? BookCase {
            record["name"] = bookCase.name as CKRecordValue?
            record["explain"] = bookCase.explain as CKRecordValue?
            record["meaning"] = bookCase.meaning as CKRecordValue?
            record["image"] = bookCase.image as CKRecordValue?
            record["word"] = bookCase.word as CKRecordValue?
        } else if let wordEntity = object as? WordEntity {
            record["antonym"] = wordEntity.antonym as CKRecordValue?
            record["bookCaseName"] = wordEntity.bookCaseName as CKRecordValue?
            record["date"] = wordEntity.date as CKRecordValue?
            record["definition"] = wordEntity.definition as CKRecordValue?
            record["detail"] = wordEntity.detail as CKRecordValue?
            record["memory"] = wordEntity.memory as CKRecordValue?
            record["pronunciation"] = wordEntity.pronunciation as CKRecordValue?
            record["synonym"] = wordEntity.synonym as CKRecordValue?
            record["word"] = wordEntity.word as CKRecordValue?
        }
    }
    
func saveRecord(_ record: CKRecord, toDatabase database: CKDatabase) {
        database.save(record) { savedRecord, error in
            if let error = error {
                print("Error saving record to CloudKit: \(error)")
            } else {
                print("\(record.recordType) saved to CloudKit successfully")
            }
        }
    }

여기서 변경점은 uuid를 각 entity마다 attribute로 추가해주었다는 점이다.

나름대로 분석.

GPT가 알려준 코드를 내 나름대로 분석을 해본다.

내가 이해한 매커니즘은 다음과 같다.

우선 syncEntity를 호출하면서 이때 우리가 사용하는 Entity에 대한 값을 가져온다.

함수를 2번 호출하기에 그중에서도 Attribute 가 많은 WordEntity로 기준을 하여 작성해본다.

이때 제너릭을 사용하는데, NSManagedObject 즉 coredata의 Entity를 가져온다.

1. syncEntity

WordEntity를 요청하는 request 변수를 만든다. 그리고 값을 가져오게 한다.

그리고 wordEntity에 uuid가 있는지 확인하고 없다면 값을 부여하게 된다.

2. SaveToCloudKit

저장할 database를 만들고,

entity에 해당하는 uuid를 가진 recordID를 만든다.

그리고 그걸 이용하여 database에 작업을 수행한다.

값이 존재할경우, 그리고 만약 업데이트가 필요하면 (값이바뀌거나) 그럴때 업데이트를 하고, 값이 없을땐 등록을 해준다.

1. populateRecord

해당 코드를 통해 key, value 형식으로 어떻게 저장이 될건지를 세팅

2. saveRecord

데이터베이스에 저장.

뭐 이런식으로 가게된다.

이건 주말에 다시 정리를 해야할듯…

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.