Danny의 iOS 컨닝페이퍼
article thumbnail

[TIL #23] 2023 / 05 / 16

Realm 사용 시, 유용한 코드들을 정리하려고 합니다.

 

 

Realm 모델 만드는 방법

기존에는 @objc Dynamic를 사용했는데, 이제는 @Persisted를 사용한다고 하네요.

@Persisted은 Realm에서 만든 일종의 프로퍼티 래퍼(미리 어떤 행동을 할지 선언과 동시에 정하는 기능)입니다.

// Object은 hashable로 동작합니다.
class RealmModel: Object { 
    // primaryKey 일종의 UUID라고 생각하면 됩니다. (ObjectId 대신 UUID를 사용해도 됩니다)
    // 중복 방지하고 CRUD에서 원하는 데이터에 접근하기 위해 사용합니다.
    @Persisted(primaryKey: true) var id: ObjectId
    @Persisted var name: String?
    @Persisted var gender: String?
    
    // 생성자를 만들어 주고 싶으면 이와 같이 convenience를 사용하고 self.init() 후 사용
    convenience init(name: String? = nil, gender: String? = nil) {
        self.init()
        
        self.name = name
        self.gender = gender
    }
}

 

 

CRUD 사용하기

모델을 만들어 봤으니, CRUD 방법을 간단히 알아봅시다.

 

Realm을 사용하기 위해 Realm 객체를 만들어주고, 또한 사용할 모델 객체도 만들어 줍시다.

var realm = try! Realm()
var model1 = RealmModel(name: "Danny", gender: "Male")
var model2 = RealmModel(name: "Jane", gender: "Female")

 

생성, 수정, 삭제 시 무조건 write 메서드의 컴플리션 블록 안쪽에서 정의를 해줘야 합니다.

 

Create

단일, 여러 모델을 추가가 가능합니다.

func createRealm() {
    do {
        try realm.write {
            realm.add(model1)
            // 이렇게 배열로 묶어서 여러개 추가도 가능하다
            // realm.add([model1, model2])
        }
    } catch {
        print("Failed to create")
    }
}

 

Read

원하는 특정 데이터 및 모든 모델 데이터 찾기

// 1. PrimaryKey를 통해 특정 모델 데이터 찾기
func loadRealm() {
    // 1. PrimaryKey를 통해 특정 모델 데이터 찾기
    let primaryKey = model1.id
    let model = realm.object(ofType: RealmModel.self, forPrimaryKey: primaryKey)
}
// 2. 모든 모델 데이터 얻기
func loadRealm() {
    let models = realm.objects(RealmModel.self)
}

 

Update

기본 사용 방법은 아래와 같습니다.

func updateRealm() {
    let firstModel = realm.objects(RealmModel.self).first!
    
    do {
        try realm.write {
            firstModel.name = "대니"
            firstModel.gender = "남자"
            
            // Key-Value 값으로 변경도 가능
            // firstModel.setValue("대니", forKey: "name")
            // firstModel.setValue("남자", forKey: "gender")
        }
    } catch {
        print("Failed to update")
    }
}

 

두 번째 방법, 이미 저장 된 객체가 존재하고 primaryKey를 알고 있다면, primaryKey를 갖고 업데이트도 가능합니다.

참고. 같은 primaryKey(id)로 새로운 모델을 만들어 저장하면 앱이 터집니다.

(여긴, "add(newModel, update: .modified)"를 사용합니다)

func updateRealmWithPrimaryKey() {
    let primaryKey = model1.id
    let newModel = RealmModel(value: ["id": primaryKey,
                                      "name": "대니",
                                      "gender": "남자"])
    
    do {
        try realm.write {
            realm.add(newModel, update: .modified)
        }
    } catch {
        print("Failed to update")
    }
}

다시, 간단하게 말해서 primaryKey를 알고 있다면, 업데이트 방법은 Create와 거의 동일하며

다만, add 메서드에서 update가 포함돼 있는 메서드를 사용해 주면 됩니다.

realm.add(newModel, update: .modified)

 

 

Delete

모델을 불러와서 삭제하기 및 전체 삭제

만약, PrimaryKey를 알고 있다면 특정 모델을 불러와서 지워주면 되겠죠?

func deleteRealm() {
    let firstModel = realm.objects(RealmModel.self).first!
    
    do {
        try realm.write {
            realm.delete(firstModel)
            // 모든 모델 데이터를 삭제하는 방법
            // realm.deleteAll()
        }
    } catch {
        print("Failed to delete")

    }
}

 

 

필터 기능

where절을 사용하여, Swift의 거의 모든 논리(술어)를 사용할 수 있습니다.

 

where절 안쪽에서 아래 기능들을 사용할 수 있습니다.

// Prefix
NOT ! swift let results = realm.objects(Person.self).query { !$0.dogsName.contains("Fido") || !$0.name.contains("Foo") }

// Comparisions
Equals ==
Not Equals !=
Greater Than >
Less Than <
Greater Than or Equal >=
Less Than or Equal <=
Between .contains(_ range:)

// Collections
IN .contains(_ element:)
Between .contains(_ range:)

// Map
@allKeys .keys
@allValues .values

// Compound
AND &&
OR ||

// Collection Aggregation
@avg .avg
@min .min
@max .max
@sum .sum
@count .count swift let results = realm.objects(Person.self).query { !$0.dogs.age.avg >= 0 || !$0.dogsAgesArray.avg >= 0 }

// Other
NOT !
Subquery ($0.fooList.intCol >= 5).count > n

 

간단히 Where를 사용해 비교하는 예제

func filterRealm() {
    // 전체 모델에서 name이 Danny인 데이터만 필터링
    let filterModels = realm.objects(RealmModel.self).where {
        $0.name == "Danny"
    }
    
    print(filterModels)
}

 

추가로 sort도 가능합니다.

모델들의 name을 비교해서 오름차순으로 정렬하기

func sortRealm() {
    let models = realm.objects(RealmModel.self)
    let sortedModels = models.sorted(byKeyPath: "name", ascending: true)
    
    print(sortedModels)
}

 

 

Realm의 Data Base 파일 자체를 삭제하기

파일을 FInder에서 찾아서 지우는 방법 말고,

Xcode 내장 클래스인 FileManager를 이용하여 코드로 파일을 지우는 방법이 있습니다.

// 저장된 DB 파일 경로
print(Realm.Configuration.defaultConfiguration.fileURL)

// 프린트를 찍어보면 (file:///Users/.../Documents/default.realm) 이와 같이 경로가 찍힙니다.
// 이 경로상에서 제거할 파일의 확장자 추가를 하고 FileManager를 통해 파일을 삭제합니다.
let realmURL = Realm.Configuration.defaultConfiguration.fileURL!

let realmURLs = [
    realmURL,
    realmURL.appendingPathExtension("lock"),
    realmURL.appendingPathExtension("management")
]

for URL in realmURLs {
    do {
        // 파일 제거하기
        try FileManager.default.removeItem(at: URL)
    } catch {
        print("Faild to delete file")
    }
}

참고 - https://pipe0502.tistory.com/entry/Realm-Swift

 

 

데이터 읽을 때, Results 타입

이와 같이 데이터를 읽을 때, Results 타입으로 모델이 만들어집니다.

여기서 Results는 Lazy 하게 동작합니다.

let models: Results<RealmModel> = realm.objects(RealmModel.self)

 

 

Lazy 하게 동작하는 이유

Realm은 데이터베이스에서 데이터를 효율적으로 로드하고 처리하기 위해서,

Lazy로 동작하게 만들어 줬다고 합니다.

 

여기서 문제

테이블뷰나 컬렉션뷰에 데이터를 뿌려주는 건 문제가 없지만,

만약 map이나 filtter으로 변경하게 되면, LazySequence로 타입이 변경됩니다.

let filterModels: LazyFilterSequence<Results<RealmModel>> = models.filter { $0.gender == "남자"}

복잡해 보이는 타입인데요...

 

그런데 해결방법은 단순하더라고요.

RealmModel 타입을 사용하려면 그냥 Array로 캐스팅을 해주면 해결됩니다.

let filterModels: LazyFilterSequence<Results<RealmModel>> = models.filter { $0.gender == "남자"}
let models: [RealmModel] = Array(filterModels)

 

 

 

 

 

 

 

계속 업데이트 중...

 

 

 

 

 

 

 

 

반응형
profile

Danny의 iOS 컨닝페이퍼

@Danny's iOS

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!