티스토리 뷰

Programming/IOS

Core Data (1)

글그리 2021. 11. 8. 23:21

iOS 개발 중 로컬에 데이터를 저장할 때 UserDefault를 주로 사용했는데, UserDefault는 Key-Value로 저장되는 데이터라서 비교적 간단한 데이터는 저장해도 괜찮을 것 같지만 데이터베이스 역할을 하기에는 부적절해보였다.

CoreData는 애플에서 제공하는 프레임워크로, 데이터베이스를 만들고 접근할 수 있는 방법을 제공한다.

아무래도 이론적인 부분을 파고들기 보다는 간단한 예제를 통해 기본적인 흐름을 파악하는게 익숙하기 때문에 간단한 예제를 만들어본 후에 이론적인 부분을 살펴보도록 하겠다.

예제 프로젝트

먼저 iOS 프로젝트를 생성한다. 여기서 Use Core Data를 체크하면 자동으로 데이터베이스 파일이 만들어지고 기본적인 접근 코드가 AppDelegate에 생성된다. (물론 체크하지 않고 만들어도 나중에 추가할 수 있다.)

CoreData를 테스트하기 위한 프로젝트니까 Use Core Data를 체크 하고 생성하면 AppDelegate 클래스 안에 기본적인 CoreData코드가 생성되고 프로젝트 이름과 똑같은 .xcdatamodeld파일이 생긴 것을 볼 수 있다. 먼저 AppDelegate 코드를 보면 어떻게 사용하는지 알 수 있을 것 같다.

// MARK: - Core Data stack

lazy var persistentContainer: NSPersistentContainer = {
    let container = NSPersistentContainer(name: "CoreDataTest")
    container.loadPersistentStores(completionHandler: { (storeDescription, error) in
        if let error = error as NSError? {
            fatalError("Unresolved error \(error), \(error.userInfo)")
        }
    })
    return container
}()

// MARK: - Core Data Saving support

func saveContext () {
    let context = persistentContainer.viewContext
    if context.hasChanges {
        do {
            try context.save()
        } catch {
            let nserror = error as NSError
            fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
        }
    }
}

persistentContainer라는 인스턴스를 사용해서 데이터베이스에 접근하고 저장할 수 있는 것 같다. container를 만드는 코드를 보니 프로젝트를 생성할 때 기본적으로 생성된 .xcdatamodeld파일 이름이랑 똑같은 이름으로 만드는 것을 볼 수 있다.

그럼 이제 이 파일을 열어보자.

Xcode 버전마다 UI는 다를 수 있지만 위 모습에서 크게 벗어나지는 않을 것이다. 데이터베이스에 접근할 수 있는 프레임워크라고 했으니까 데이터베이스 용어를 사용해서 설명하자면 Entity는 데이터베이스의 테이블이라고 볼 수 있다. 지금은 아무 테이블도 없는 상태이니 테스트에 사용할 테이블을 하나 만들어보자. 우측 하단에 있는 Add Entity버튼을 눌러서 Entity를 만들 수 있다.

이름은 Person이라고 지었다. 그리고 오른쪽 페이지에 보이는 Attribute가 바로 데이터베이스의 Row이름이다. 사람 정보를 표현하기 위해 Entity의 이름을 Person이라고 지었으니, 그에 걸맞는 Attribute를 몇 개 추가해준다.

Attribute는 기본적인 데이터 타입(Int, String, Double, Float, Bool 등)을 가질 수 있다. 지금은 이름과 나이만 추가했기 때문에 이런 타입으로도 충분해 보이지만 나중에 더 복잡한 데이터가 필요하게 될 경우에는 Transformable을 사용해서 커스텀 오브젝트 타입도 사용할 수 있다고 한다.

일단 빠르게 동작하는 모습을 확인하려고 만드는 예제니까 쉽게 String이랑 Decimal만 추가한 후에 ViewController로 이동해서 실제로 데이터를 만들어서 저장하는 코드를 작성해보자. 먼저 AppDelegate에 정의된 container에서 context를 얻어야 한다.

if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
    let context = appDelegate.persistentContainer.viewContext

}

context를 통해 위에서 정의한 entity에 접근할 수 있다. CoreData 프레임워크는 사용자가 정의한 entity들을 모두 NSManagedObject라는 클래스의 서브 클래스로 만들어서 관리한다. 위에서 만든 Person또한 CoreData 프레임워크에 의해 같은 이름을 가진 NSManagedObject 클래스로 자동으로(자동으로 생성하지 않도록 한 후 프로그래머가 임의로 추가할 수도 있다.) 정의된다. 어쨋든 데이터베이스에 값을 추가하기 위해 해당 entity에 해당하는 NSManagedObject 인스턴스를 만들어야 한다.

let description= NSEntityDescription.entity(forEntityName: "Person", in: context)!
let person = Person(entity: description, into: context)

NSManagedObject를 상속받는 Person이라는 클래스가 자동으로 생성된다고 했기 때문에 Person클래스를 따로 정의하지 않아도 바로 사용할 수 있다. 정의된 코드를 보면 NSManagedObject를 상속받은 것을 볼 수 있을 것이다. 이 클래스에는 위에서 추가한 name, age 등 attribute들도 자동으로 생성되어 있다.

임의로 하나의 Person 데이터를 만들어봤다.

if let description = NSEntityDescription.entity(forEntityName: "Person", in: context) {
    let person = Person(entity: description, into: context)
    person.name = "미랑이"
    person.age = 12
}

저장은 더 간단하다. NSPersistentContainer에서 context를 얻어왔고, 그 안에 Person 인스턴스를 만들어서 값을 세팅했기 때문에 context를 저장하기만 하면 된다.

do {
    try context.save()
} catch {
    print(error.localizedDescription)
}

viewDidLoad에 코드를 추가했으니 실행만 하면 미랑이라는 person 데이터가 저장될 것이다. 실제로 저장되었는지 확인하기 위해 sqlite 파일을 열어볼 수도 있는데, 이 때 저장되는 파일의 위치를 알아내기 위해서 AppScheme 설정에 들어가 아래 두 argument를 추가한다.

  • -com.apple.CoreData.SQLDebug 1
  • -com.apple.CoreData.Logging.stderr 1

앱을 실행하면 콘솔창에 생성된 sqlite 파일의 경로가 출력되는 것을 볼 수 있다.

/Library/Developer/CoreSimulator/Devices/{시뮬레이터 아이디}/data/Containers/Data/Application/{앱 아이디}/Library/Application Support/CoreDataTest.sqlite

해당 파일을 열어보면(sqlite 파일을 열어볼 수 있는 다양한 에디터들이 있다.) 추가한 미랑이 정보가 있다.

보통 데이터베이스에서 정보를 가져올 때 쿼리문을 사용하는데 CoreData 프레임워크는 쿼리문을 사용하지 않고 제공하는 함수를 사용해서 데이터베이스를 조회할 수 있다. 기본적으로 수많은 데이터를 저장하기 때문에 배열로 조회가 된다.

do {
    let people = try context.fetch(Person.fetchRequest())

    people.forEach { person in
        print(person.name!)  // 미랑이
    }
} catch {
    print(error.localizedDescription)
}

근데 entity를 만들 때 분명히 Optional을 체크하지 않았는데 name이 왜 String?이 된건지는 잘 모르겠다.

데이터를 저장하고 불러오는 간단한 예제를 빠르게 만들어봤으니 이제 이 예제를 하나씩 되짚어가면서 CoreData 프레임워크의 Core를 하나씩 알아보자.

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
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
글 보관함