본문 바로가기

IOS/CoreData

CoreData MultiThreading과 Delete Rule

코데 사용시 겪었던 문제

저희 앱은 MultiPlatform 환경의 사용자 또는 MultiDevice 사용자를 위한 Data Migration 작업이 필요했다. 예를 들어, 사용자가 A기기에서 B기기로 사용 기기를 바꾼다면 저희 앱은 서버에 백업용으로 저장해왔던 해당 유저의 암기장 데이터들을 사용해서 재로그인 시 동기화 작업을 수행했습니다. 이 과정에서 서버에 있는 많은 Note와 그에 포함되어 있는 Word들을 불러오고 거의 동시에 그 데이터들을 코데에 저장하는 과정에서 지속적으로 CoreData에 저장시키지 못하고 누락되는 버그들이 생겼습니다.

 

  • 찾았던 문제점들
    1. NoteEntity와 WordEntity의 Relationship 설정이 1 : N 관계였는데 이 데이터들을 삭제하는 규칙(Delete Rule)을 설정함에 있어서 실수가 있었습니다.
    2. 코어데이터는 MultiThread 환경에서 작업 가능하게 설계되었지만, 모든 객체가 Thread Safe한 것은 아니다(apple 공식문서). 하지만 우리 앱은 코어데이터를 사용하는 중에 Thread를 무분별하게 사용하면서 Save 오류가 났습니다.
  • 해결 방법
    1. 기존 Default Option이었던 Nullify 삭제 규칙이 NoteEntity와 그에 포함된 WordEntity들을 동시에 삭제시켜주지 못 해서 Delete Rule을 Cascade로 바꿔주었고 문제를 해결했습니다.
    2. 코어데이터를 Thread Safe하게 사용하려면, 두 가지 유형의 managed object contexts (Main Queue와 Private Queue)에서만 작업을 해야했다. 해당 사항을 기존에는 전혀 고려하지 않았지만, 서버 데이터들을 코어데이터에 Save 하는 모든 과정을 Main Queue에서 진행시키면서 문제를 해결했었지만 더 많은 데이터들을 동기화할 때 역시 원활하게 작동할 수 있도록 Private Queue로 전환해주며 동기화시 UI 멈춤 현상을 더 개선하였습니다.
class CoreDataManager {

    static let instance = CoreDataManager()
    let container: NSPersistentContainer
    let context: NSManagedObjectContext
    init() {
        container = NSPersistentContainer(name: "CoreDataModel")
        container.loadPersistentStores { (_, error) in
            if let error {
                print("Error occured. \(error)")
            }
        }
        context = container.newBackgroundContext()
    }

    func save() {
        do {
            try context.save()
        } catch {
            print("Error occured in saving. \(error.localizedDescription)")
        }
    }
}





// private Queue를 사용한 메서드
func addNoteAndWord<T: NoteProtocol>(note: T,
                                         words: [Word],
                                         _ repeatCount: Int? = nil,
                                         firstTestResult: Double? = nil,
                                         lastTestResult: Double? = nil,
                                         nextStudyDate: Date? = nil)
    {
        manager.context.perform {
            let returnedNote = self.returnNote(
                       id: note.id,
                       noteName: note.noteName,
                       enrollmentUser: note.enrollmentUser,
                       noteCategory: note.noteCategory,
                       repeatCount: repeatCount ?? 0,
                       firstTestResult: firstTestResult ?? 0,
                       lastTestResult: lastTestResult ?? 0,
                       updateDate: note.updateDate,
                       nextStudyDate : nextStudyDate ?? Date()
            )
            for word in words {
                let newWord = WordEntity(context: self.manager.context)
                newWord.id = word.id
                newWord.wordLevel = Int64(word.wordLevel)
                newWord.wordMeaning = word.wordMeaning
                newWord.wordString = word.wordString

                returnedNote.addToWords(newWord)

            }
            self.save()
        }
    }

https://youtube.com/shorts/T_-kS3cy0kc

https://youtube.com/shorts/LkJ6sitmSVQ?feature=share

참고 자료

멀티스레딩

CoreData는 기본적으로 싱글 스레드 환경에서 동작한다. 그러나 CoreData를 사용하는 앱에서는 멀티스레딩이 필요한 경우가 많다. CoreData에서 멀티스레딩을 구현하는 방법은 다음과 같다.

NSManagedObjectContext의 Concurrency Type

NSManagedObjectContext는 CoreData에서 가장 중요한 클래스 중 하나이다. CoreData에서 데이터를 생성, 수정, 삭제하는 모든 작업은 NSManagedObjectContext에서 이루어진다. NSManagedObjectContext는 Concurrency Type이라는 속성을 가지고 있으며, 이 속성을 사용하여 멀티스레딩을 구현할 수 있다.

Concurrency Type은 다음과 같이 두 가지가 있다.

  1. NSMainQueueConcurrencyType
  2. NSPrivateQueueConcurrencyType

NSMainQueueConcurrencyType은 메인 스레드에서 작업하는 경우에 사용하며, NSPrivateQueueConcurrencyType은 백그라운드 스레드에서 작업하는 경우에 사용한다.

삭제 규칙

CoreData에서는 관련된 오브젝트들의 연결성을 유지하기 위해 삭제 규칙을 지원한다.

삭제 규칙의 종류

  1. Nullify
  2. Cascade
  3. Deny

Nullify

Nullify는 관련된 오브젝트들의 연결성을 끊는 것이다. 예를 들어, 부서와 직원이 관련된 경우, 부서를 삭제하면 해당 부서에 속한 모든 직원의 부서 속성을 null로 설정한다.

Cascade

Cascade는 관련된 오브젝트들을 함께 삭제하는 것이다. 예를 들어, 부서와 직원이 관련된 경우, 부서를 삭제하면 해당 부서에 속한 모든 직원도 함께 삭제된다.

Deny

Deny는 삭제를 금지하는 것이다. 예를 들어, 부서와 직원이 관련된 경우, 부서가 삭제되지 않도록 금지할 수 있다.

참고 :

https://eunjin3786.tistory.com/153

https://developer.apple.com/documentation/coredata/using_core_data_in_the_background#2904034