단어장 프로젝트 (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)
}
그리고 단어장이 있는 상태에서 설정하기를 누를때 마지막으로 한번더 체크
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)
}
}
시작할때 데이터를 못받는 문제가 있어서 위치 조정
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)
}
}
완료.
CloudKit 사용
여기서 추가.
그다음 cloudkit을 체크하고
아래 + 버튼을 클릭하여 identifier이름의 컨테이너를 추가해본다.
cloudkit console을 들어가면 추가한 identifier에 해당하는 내용이 보인다.
여기까지 세팅은 여기를 참고했다.
물론 빠졌지만 background도 해주었다.
Background modes - Remote Notifications를 체크하는 이유는 새로운 컨텐츠가 생겼을 때, 어떠한 알림 없이 조용하게 알리기 위해 사용한다.
여기서 부터는 여기를 참고한다. 필요한 Docs를 별도로 정리해 줘서 그걸 보면서 만들 수 있을 듯 하다.
그리고 우리가 만든 코어데이터로 가서
여기를 체크해주었다.
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
데이터베이스에 저장.
뭐 이런식으로 가게된다.
이건 주말에 다시 정리를 해야할듯…