iOS

[iOS] Realm에 대해 알아보자(feat. SwiftUI + TCA)

Hatchling.dev 2024. 2. 28. 14:06

(편의상 편한 말투로 작성하는 점 이해 부탁드립니다.😅)

(부정확한 정보가 있을 수 있습니다. 지적 환영🤗)

 

안녕하세요! Hatchling입니다.

 

오늘은 Realm에 대해 알아보려고 합니다.

Realm은 이미 많은 분들이 사용하고 계시고 iOS를 개발하면서 내부에 데이터를 저장할 때 많이 쓰는 라이브러리인데요!

 

실제로 제가 사이드 프로젝트에 도입하면서 공부했던 내용과 겪었던 이슈들을 정리하는 시간을 가져보려고 합니당!

 

Realm?

Realm은 오픈소스 데이터베이스(DBMS)로 모바일을 주요 타깃으로 삼은 데이터베이스 라이브러리입니다.

iOS에서는 기본적으로 제공하는 UserDefaults, CoreData가 있고 외부 라이브러리인 SQLite도 있어 이들과 많이 비교를 하곤 하는데요!

위에서 말한 것들 모두 디바이스 내부에 데이터를 저장하는 기능을 제공하지만 이 모든 걸 한 번에 정리하기에는 글이 너무 길어질 것 같아 간단하게 정리하자면

UserDefaults : Key-Value를 쌍으로 데이터를 저장하는 가장 기본적인 방법인데요! 사용하기도 정말 쉽고 빠르고 간단하기 때문에 편리하지만 데이터를 plist에 XML 형태로 저장하기 때문에 항상 메모리에 올라와 있어 오버헤드가 발생하진 않지만 데이터 양이 많아진다면 그만큼 메모리를 차지하기 때문에 소규모 데이터에 적합합니다.

CoreData : 데이터베이스 기능을 제공하는 프레임워크. (데이터베이스 아님🙅‍♂️) CoreData는 SQLite나 Realm과는 달리 객체를 중심으로 데이터베이스 기능을 제공합니다. 속도는 SQLite와 비슷한 것으로 알고 있습니다.

SQLite : 오픈소스 데이터베이스. MacOS, Windows, Linux, iOS, Android에서 사용할 수 있습니다. SQLite는 가볍고 범용성이 좋아 전세계적으로 많이 사용되고 있습니다.

Realm : 오픈소스 데이터베이스. 모바일에 최적화 되어 있으며 데이터 컨테이너 모델을 사용하여 객체 단위로 저장합니다. Key-Value가 아닌 객체 단위이기 때문에 별도 매핑 과정이 필요 없어 속도가 SQLite나 CoreData보다 빠릅니다. 그리고 대규모 데이터를 저장해도 무료로 사용 가능하며 대규모 데이터, 스토리지에 상관 없이 일관적인 속도와 성능을 유지할 수 있습니다. 또! iOS와 Android 간의 데이터를 공유할 수 있습니다.

 

최대한 간단하게 설명해보려고 했는데 그래도 좀 길어진 느낌이 없지 않아 있네요..🥲

 

 

SwiftUI에서 Realm 적용하기 (feat. CRUD)

Realm이 뭔지에 대해 알아보았으니 본격적으로 어떻게 사용할 수 있는지 예제 코드와 함께 알아보도록 하겠습니다!!!

코드만 봐도 이해가 될 정도로 정말 쉽습니다.

 

먼저 Realm에서 사용할 객체를 정의해 보겠습니다.

Realm에서는 Persisted라는 property wrapper를 사용합니다.

primaryKey도 지정해 줘야 하는데 중복을 피하기 위해 Realm에서 제공하는 ObjectId를 사용해 주었습니다!

final class Question: Object, ObjectKeyIdentifiable {
    @Persisted(primaryKey: true) var _id: ObjectId
    @Persisted var title: String
    
    convenience init(title: String) {
        self.init()
        self._id = ObjectId.generate()
        self.title = title
    }
}

 

1. Create

Realm 객체를 생성하고 .add 메서드를 활용해 원하는 데이터를 추가할 수 있습니다.

do {
    let realm = try Realm()
    try realm.write {
        realm.add(Object)
    }
} catch {
    throw RealmFailure
}

 

2. Read

Realm 객체를 생성하고 .objects 메서드에 읽고 싶은 객체 타입을 넣으면 끝!

do {
    let realm = try Realm()
    return realm.objects(Object.self)
} catch {
    throw RealmFailure
}

 

3. Update

Create와 비슷합니다. Realm 객체를 생성하고 기존 데이터를 수정하여 .create 메서드를 활용해 수정할 수 있습니다.

저는 여기서 Attempting to modify object outside of a write transaction - call beginWriteTransaction on an RLMRealm instance first.’ 라는 에러가 발생한 적이 있는데 해당 에러는 realm.write 클로저 밖에서 객체의 수정이 일어나면 발생하는 에러라고 합니다.

만약 객체의 수정이 필요하다면 realm.write 클로저 안에서 수정하고 업데이트하는 과정이 필요할 것 같네요!!

do {
    let realm = try Realm()
    try realm.write {
        let object = object
        object.count += 1
        realm.create(Object.self, value: object, update: .modified)
    }
} catch {
    throw RealmFailure
}

 

4. Delete

마지막으로 delete입니다. 삭제하고 싶은 객체의 타입과 primaryKey를 활용하여 데이터를 삭제할 수 있습니다!

do {
    let realm = try Realm()
    try realm.write {
        guard let object = realm.object(ofType: Object.self, forPrimaryKey: id) else { throw RealmFailure }
        realm.delete(object)
    }
} catch {
    throw RealmFailure
}

 

 

이렇게 Realm이 무엇인지, 기본적인 사용법들을 알아보았는데요! 

다음은 제가 프로젝트를 진행하면서 겪었던 이슈를 해결한 경험을 정리해보겠습니다.

 

Issue

Terminating app due to uncaught exception 'RLMException', reason: 'Realm accessed from incorrect thread.’

잘못된 스레드에서 접근을 시도했다는 내용의 에러입니다..

 

제 사이드 프로젝트는 SwiftUI에 TCA를 적용했는데요 (아직 TCA 사용 후기를 포스팅하지 못하고 있는데 (밀리고 밀려..) TCA에 대한 내용은 추후에 자세히 포스팅 하도록 하겠슴돠)

 

TCA에 대해 간단하게 설명하자면 사용자의 이벤트를 받아 상태를 관리하는 라이브러리입니다.

사용자의 이벤트인 Action 함수를 통해 상태를 변화시키거나 effect를 발생시키는 역할을 하게 되는데 Realm을 사용하고 effect를 발생시키기 위해 비동기 함수를 사용해서 스레드 관련 에러가 발생했던 것이었습니다....(I'm 멍청이에요)

 

여러분은 저같은 실수는 하지 마세요😆

 

 

오늘은 Realm에 대해 알아보았는데요!

아직 SwiftUI(신생..?) + TCA(신생) + Realm 조합을 사용해보니 아직 레퍼런스가 그렇게 많진 않은 것 같아 한 번 정리해보는 시간을 가져보았습니다.

혹시 모를 누군가에 도움이 되기를 바라면서 저는 다음 포스팅으로 돌아오겠습니다!!