먹고 기도하고 코딩하라

[Swift] Realm (4) Best practices and most common mistakes 본문

앱/Swift

[Swift] Realm (4) Best practices and most common mistakes

사과먹는사람 2023. 5. 8. 21:17
728x90
728x90

이번 포스팅은 Realm 시리즈의 최종장으로 realm 사용에 있어서 자주 실수하는 것들과 더 나아가 realm을 더 잘 쓸 수 있는 best practices를 소개한다.

 

Realm에 접근해야 할 때마다 try! Realm() 하기

저번 포스팅에도 썼던 것처럼, Realm을 접근할 때마다 try! Realm()으로 Realm 객체를 그때그때 가져와주는 것이 좋다. 스레드에서 처음 접근할 때 초기화해서 객체를 생성한 뒤로는 그것을 계속 캐싱해서 쓰기 때문에 오버헤드는 그다지 없다. 게다가 다른 스레드에서 쓰려고 하면, ThreadSafeReference로 Realm을 가져가서 써야 하기 때문에 불편하다. 특별한 사유가 없다면 접근할 때마다 Realm 객체를 가져온다.

// 가급적 피하세요
static let realm = try! Realm()
let butterDog = realm.object(ofType: Dog.self, forPrimaryKey: "butter")

// ...
// 이런 경우 문제가 됩니다
doLaterCloser { _ in 
  // 다른 스레드에서 실행되는 컴플리션 핸들러일 때, 앱이 죽습니다
  let chocoDog = realm.object(ofType: Dog.self, forPrimaryKey: "choco")
}
// 대신 이렇게 하세요
let realm = try! Realm()
let butterDog = realm.object(ofType: Dog.self, forPrimaryKey: "butter")

// ...
doLaterCloser { _ in 
  let realm = try! Realm()
  // 이 스레드에서 realm 객체를 선언하지 않았다면 새로 만들어질 것이고,
  // 이미 선언했다면 캐시된 realm을 사용할 겁니다
  let chocoDog = realm.object(ofType: Dog.self, forPrimaryKey: "choco")
}

 

Live Object를 저장해놓고 쓰지 말 것

Live Object를 저장해놓고 쓰고자 할 때가 있는데, 원본을 계속 갖고 있지 말고 복사해서 쓰든지 해야 한다. Live Object란, realm.object, realm.objects 등으로 Realm 데이터베이스에 저장된 객체를 의미하는데 이것도 결국 Live Object를 불러온 스레드에 종속되기 때문에 다른 스레드에서 쓰려고 하면 앱이 죽는다.

// 가급적 피하세요
let realm = try! Realm()
let butterDog = realm.object(ofType: Dog.self, forPrimaryKey: "butter")

// ...
// 이런 경우 문제가 됩니다
doLaterCloser { _ in 
  // 다른 스레드에서 Live Object에 접근할 때, 앱이 죽습니다
  let birthday = butterDog?.birthday
}
// 대신 이렇게 하세요
let realm = try! Realm()

doLaterCloser { _ in 
  // 해당 스레드에서 가져온 것이므로 안전하게 사용할 수 있습니다
  let birthday = realm.object(ofType: Dog.self, forPrimaryKey: "butter")?.birthday
}
// 혹은 이렇게 하세요
let realm = try! Realm()
let butterDog = realm.object(ofType: Dog.self, forPrimaryKey: "butter")
let cloneButterDog = Dog(value: butter)

doLaterCloser { _ in 
  // 아직 realm에 저장되지 않아서 Live Object가 아니기 때문에 자유롭게 사용할 수 있습니다.
  let birthday = cloneButterDog?.birthday
}

 

백그라운드에서 write 작업을 한다면 UI 스레드에서의 동기 write 작업을 피할 것

Realm 파일은 어느 스레드에서나 접근이 가능하지만, 여러 군데에서 동시에 write 작업을 할 수는 없다. 결과적으로, 동기적인 write 트랜잭션은 서로의 작업을 블락하게 되어 있다. UI 스레드(메인 스레드)에서의 동기 write 작업은 앱의 반응성에 영향을 줄 수 있다.

writeAsync를 사용하거나 백그라운드에서 write 작업을 하는 것을 대안으로 꼽을 수 있다.

 

벌크 데이터를 처리해야 한다면, 청크로 나눠서 여러 트랜잭션으로 분할할 것

1,000개 정도 되는 데이터를 처리해야 한다면 1,000번 write하는 방법이 있고, 1,000개 데이터를 배열에 담아서 한 번에 add시켜서 1번 write하는 방법이 있으며, 500개 단위로 나눠 2번 write하는 방법이 있다. 시간으로 따졌을 때는 1,000번 write하는 게 가장 오랜 시간이 걸리며, 1번 write하는 게 가장 적은 시간이 걸린다. 즉, 대부분의 경우 트랜잭션의 횟수가 작업 시간을 결정한다.

몇 개씩 나눠서 write하는 것이 좋은지에 대한 가이드라인이 명확하게 있는 건 아니지만, 한 트랜잭션에 너무 많은 데이터를 담는 것도 좋지 않고, 또 트랜잭션당 데이터가 너무 적어서 트랜잭션이 많이 일어나는 것도 그다지 좋지 않으므로 밸런스를 맞출 수 있도록 개수를 지정해두는 것이 좋다.

var dogs: [Dog] = []
// dogs에 1,000개의 Dog 객체를 집어넣는다

// 이렇게 하면 1,000번의 트랜잭션이 일어납니다. 속도가 느려질 수 있습니다.
for i in 0..<1000 {
  let realm = try! Realm()
  try! realm.write {
    realm.add(dogs[i])
  }
}
var dogs: [Dog] = []
// dogs에 1,000개의 Dog 객체를 집어넣는다

// 한 번의 트랜잭션만 일어납니다.
let realm = try! Realm()
try! realm.write {
  realm.add(dogs)
}

 

Read 작업 내부에서 또 Read를 하는 것은 허용되지 않는다

말그대로, Realm.objects 내부에서 또 Realm.objects를 하는 것이 허용되지 않는다는 의미이다. 보통의 경우에는 이런 케이스가 나타나지 않는데, Configuration의 migrationBlock 내부에서 realm.objects를 하는 등의 만행을 저지르면 위와 같은 에러를 마주칠 수 있게 된다.

 

 

728x90
반응형
Comments