본문 바로가기

개발/Swift

[Swift] Core Data 알아보기 (2)

반응형

Core Data를 코드로 옮겨보자!

NSManagedObjectContext 클래스를 사용해 데이터베이스를 불러보자.
이를 가져오려면 NSPersistentContainer이 필요하다.
이 파일은 이전에 AppDelegate에 코드로 선언해두었다.

 AppDelegate에 lazy var이 있는건 아는데 어떻게 가져오냐?
우선 공유할 수 있는 UIApplication 복사본을 가져온다.

UIApplication.shared

shared는 UIApplication 클래스에 있는 static var 변수다. 
UIApplication의 위임자 역할을 하는 delegate변수에 접근한다.

(UIApplication.shared.delegate as! AppDelegate).persistentContainer

PersistentContainer을 가져와 context를 가져오자.

let container = (UIApplication.shared.delegate as! AppDelegate).persistentContainer
let context: NSManagedObjectContext = container.viewContext

viewContext는 NSManagedObjectContext 중에서도 오직 메인 쓰레드에만 쓰인다.
여러 UIView와 함께 사용되기 때문에 viewContext 이름을 갖게 되었다.
NSManagedObjectContext는 쓰레드 안정형이 아니다. 

(UIApplication.shared.delegate as! AppDelegate).persistentContainer
// 위 코드 대신 아래의 형태로 써보자.

static var persistentContainer: NSPersistentContainer {
    return (UIApplication.shared.delegate as! AppDelegate).persistentContainer
}

let coreDataContainer = AppDelegate.persistentContainer

당연히 viewContext에도 적용 가능하다.

static var viewContext: NSManagedObjectContext {
    return persistentContainer.viewContext
}

let context = AppDelegate.viewContext

 

 

데이터 넣어주기

let context = AppDelegate.viewContext
let store: NSManagedObject = NSEntityDescription.insertNewObject(forEntityName: "StoreData", into: context)

변수들이 어떤 값을 갖도록 설정하는 방법은 이렇다.

let storename = store.value(forKeyPath: "store.name") as? String

 

데이터 가져오기 (Querying)

NSFetchRequest는 데이터 베이스에서 원하는 것을 캡슐화한다.
1. 특정한 하나의 Entity만 나타낼 수 있다. 어떤 Entity를 가져올 것인가.
2. NSSortDescriptors(배열) Fetch로 array타입으로 돌아오는 것을 어떤 순서로 들어갈지 명시한다.
3. NSPredicate로 정확히 무엇을 원하는지 명시해야한다.

let request: NSFetchRequest<StoreData> = StoreData.fetchRequest()

 

SortDescriptor로 Array타입이 반환될 때 어떤 순서로 가져올지 정해준다.
key값을 지정할 수 있는데 key는 순서를 정렬하려는 데이터베이스 상의 속성(attribute)이다.

ascending(오름차순)과 descending(내림차순) 두 가지 방식이 있다.
selector는 어떤 메소드를 사용해서 아이템을 비교해 순서를 정할지 말해준다. 사용은 선택적이다.
localizedStandardCompare은 Mac 컴퓨터의 Finder처럼 String을 비교한다.
String에 흔하게 사용되는 함수다.

let sortDescriptor = NSSortDescriptor(
    key: "name", ascending: true,
    selector: #selector(NSString.localizedStandardCompare(_:)) // 스킵 가능
)

 

NSPredicate는 어떤 StoreData를 원하는지 말해주는 곳이다.
값을 넣는 부분이 C의 print문과 상당히 비슷하다.

let searchString = "shrimp"
let predicate = NSPredicate(format: "text contains[c] %@", searchString)
let joe: TwitterUser = ...
let predicate = NSPredicate(format: "tweeter = %@ && created > %@", joe, aDate)
let predicate = NSPredicate(format: "tweeter.screenName = %@", "hi there")
let predicate = NSPredicate(format: "tweets.text contains %@", searchString)
let request : NSFetchRequest<TwitterUser> = TwitterUser.fetchRequest()
let yesterday = Date(timeIntervalSinceNow: -24*60*60)
// 24 시간 전에 만들어진 것
request.predicate = NSPredicate(format: "any tweets.created > %@", yesterday)
// 트위터 사용자 이름으로 분류한 것
request.sortDescriptors = [NSSortDescriptor(key: "name", ascending: true)

 

Core Data 쓰레드 안정성

NSManagedObjectContent가 데이터베이스에서 다루어지는 것은 쓰레드 상에서 안전하지 않다.
context는 자신에게 생성된 queue에서만 사용될 수 있다.
context를 다른 queue로 넘기고 context를 호출하면 작동하지 않는다. 
해당하는 context는 다른 context에서 만들어졌기 때문이다.
즉 context를 생성한 queue에서 context를 처리해야 올바르게 작동한다.

그러나 우리는 앱의 빠른 반응과 유연한 사용을 위해 main queue외의 다른 queue에서 불러오고 싶다.
각각의 데이터베이스는 context를 여러개 가질 수 있다.
그리고 그 아래에 있는 데이터베이스가 멀티 쓰레드로 작동한다.
즉 데이터베이스에 접근하려는 모든 큐마다 context를 가지고 있어야 한다.

context.performBlock {
    // context로 작업하기
}

performBlock는 클로저를 인자로 받아, 아무것도 입력하거나 반환하지 않는다.
이 메소드는 클로저 안에서 일어나는 모든 일이 해당 context의 올바른 큐에서 발생하는 것을 보장한다.
이 메소드를 백그라운드 쓰레딩하는 dispatch와 혼동하지 말자.

다른 쓰레드에 있는 context를 가져오고 싶다면 iOS 10 이상부터 지원되는 performBackgroundTask 을 살펴보자.

AppDelegate.persistentContainer.performBackgroundTask { context in
    try? context.save()
}

이 메소드는 하나의 클로저를 인자로 받는다.
클로저는 전달 인자로 context를 받아 다른 쓰레드에서 올바르게 사용되는 context를 만들어준다.
performBackgroundTask는 main queue가 아닌 다른 queue를 찾은 다음,
다른 queue에서 context를 생성하고 queue안에 든 클로저를 백그라운드에서 실행한다.

단 주의점을 기억하자.

첫번째, 절대 이곳에서 viewContext를 사용하면 안된다. 
클로저의 정의에 따르면 클로저는 main queue에서 실행되지 않고 백그라운드의 별개의 queue에서 작동하기 때문이다.

두번째, context를 저장하는 것을 잊지 말자.
많은 작업을 해두고 저장하지 않으면 아무것도 하지 않은 것과 같다.
메모리 상에서 작업을 했기 때문에 이 블록이 사라지면 context를 다시 가져올 수 없기 때문이다.
힙 메모리에서 모든게 사라져 아무 일도 일어나지 않게 되는 것이다.

 

반응형

'개발 > Swift' 카테고리의 다른 글

[Swift] 테이블 뷰 -> 테이블 뷰 drag and drop  (0) 2021.04.20
[Swift] 문자열 다루기  (0) 2021.03.31
[Swift] Core Data 알아보기 (1)  (0) 2021.03.01
[Swift] File System  (0) 2021.03.01
[Swift] 객체 저장 - Archiving  (0) 2021.02.28