CoreData는 Apple에서 기본으로 제공하는 객체지향 데이터베이스인 프레임워크 입니다.
NSManagedObjectContext를 통해 CURD를 진행하며, Persistent Store Coordinator에 접근하고 Managed Object Instance를 생성하여 반환하여 데이터를 관리합니다.
기본적으로, 앱 내에 메모리를 저장할 수 있어서 최근 검색기록 및 내부데이터 등을 저장하기 위해 자주 사용됩니다.
간단한 데이터 저장이 필요한 경우라면 Side Effect가 크지 않을 수 있지만, 앱내에 저장해야할 데이터가 이미지이거나 한번에 많이 저장이 이루어진다면 멀티쓰레딩을 통해 CoreData 작업을 진행해야합니다.
하지만, 기본적으로 CoreData는 Thread Safe하지 않습니다.
container.performBackgroundTask() { (context) in
do {
try context.save()
} catch {
fatalError("Failure to save context: \(error)")
}
}
performBackgroundTask() 메소드를 통해 데이터를 관리하면, 메소드가 호출될때마다 새로운 백그라운드 스레드가 생성되면서 async하게 작업이 이루어집니다.
만약, 10개의 데이터를 저장하는 함수를 3번 호출한다면, 각각의 백그라운드 스레드가 3개 생성되어 멀티쓰레딩에 관련한 문제가 발생할 수 있습니다. NSPersistentContainer에 여러개의 NSManagedObjectContexts가 접근한다면 Merge conflicts가 발생합니다. 즉 하나의 저장소에 동시에 2개의 데이터를 write하려고 하기때문에 데이터가 올바르게 저장되지 못하거나 예상치 못한 문제가 발생할 수 있습니다.
이러한 문제점은 다음과 같은 방식으로 생각보다 쉽게 해결할 수 있습니다.
UI 업데이트와 관련된 호출은 메인쓰레드로 진행, 데이터의 저장과 삭제같이 시간이 오래걸릴 수 도 있는 작업을 단일 백그라운드 스레드에서 한번에 하나의 작업이 진행되도록 합니다.
Main Thread: Fetch Data
private lazy var context = appDelegate?.persistentContainer.viewContext
//Main Thread: NSManagedObjectContext
func fetchImage() -> [MyImage] {
var fetchingImage = [MyImage]()
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "MyImage")
do {
fetchingImage = try context?.fetch(fetchRequest) as! [MyImage]
} catch {
print("Error while fetching the image")
}
return fetchingImage
}
읽은 데이터를 보여주기 위한 UI 작업에는 Main Thread를 이용합니다. 또한, 비동기적으로 데이터가 저장될때에는 백그라운드 스레드에서 데이터 저장이 완료된 후에 데이터를 읽어 UI 업데이트를 진행하면 되겠죠?
백그라운드 스레드를 설명한 후, 추가적으로 설명하겠습니다.
단일 Background Thread: Data Write
//MARK: - Data Save and Delete Unique Background Thread
private lazy var backgroundContext: NSManagedObjectContext = {
guard let myAppDelegate = appDelegate else {
return NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
}
let newbackgroundContext = myAppDelegate.persistentContainer.newBackgroundContext()
newbackgroundContext.automaticallyMergesChangesFromParent = true
return newbackgroundContext
}()
위의 코드와 같이 BackgroundContext를 생성하면, 하나의 백그라운드 스레드에서 동일한 Context 작업이 이루어집니다.
하나의 백그라운드 스레드에서 하나의 작업을 진행하기 때문에, Data Race와 Merge Conflict에서 자유로워 지게 됩니다.
automaticallymergesChangesFromParent: 컨텍스트가 영구 저장소 코디네이터 또는 상위 컨텍스트에 저장된 변경 사항을 자동으로 병합하는지 여부를 나타내는 Bool 값입니다.
RxSwift를 이용한 데이터 삭제가 완료되었는지 판단
func deleteSpecificVinylBox(songTitle: String) {
isDeletedSpecificVinyl.onNext(false)
backgroundContext.perform { [weak self] in
do {
let fetchRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "VinylBox")
fetchRequest.predicate = NSPredicate(format: "songTitle = %@", songTitle)
let results = try self?.backgroundContext.fetch(fetchRequest) as! [NSManagedObject]
// Delete _all_ objects:
for object in results {
self?.backgroundContext.delete(object)
}
try self?.backgroundContext.save()
self?.isDeletedSpecificVinyl.onNext(true)
} catch {
print("Error delete specific func")
print(error.localizedDescription)
}
}
}
데이터 삭제가 비동기적으로 진행되고 완료된 후
isDeletedSpecificVinyl 변수에 true라는 값이 이벤트로 발생하게 됩니다.
만약 비동기적으로 데이터 삭제를 진행하고, 바로 메인쓰레드에서 Data Fetch를 진행하면 삭제가 진행되지 않은 데이터가 업데이트 되므로 유의해야합니다.
'Deep Dive iOS' 카테고리의 다른 글
간단하게 알아보는 GCD vs Swift Concurrency 차이 (0) | 2022.01.18 |
---|---|
iOS 계층구조? 4단계로 나누어 생각해보기 (iOS Structure) (0) | 2020.12.26 |
UIView? Custom View? (0) | 2020.11.12 |
앱이 inactive 상태가 되는 경우 (0) | 2020.11.11 |
iOS/Swift) UIKit 이란? (0) | 2020.11.10 |