요약
실제 기기에서는 번들에 있는 Realm
파일에 접근할 수 없어서 파일 접근 에러가 발생합니다.
이를 해결하기 위해 앱이 실행 중일 때 Realm
파일을 앱의
documentDirectory
로 복사하여 사용하는 방법을 사용할 수 있습니다.
이렇게 하면 파일에 대한 읽기 및 쓰기 권한이 부여되어 Realm
데이터베이스를 사용할 수 있습니다.
문제의 시작
운동 기록 앱을 개발할 때,
운동 데이터베이스(DB)를 생성하여 앱에 삽입하여 사용하려고 시도해 보았습니다.
Realm 파일을 만들어 프로젝트에 포함시킨 후 Bundle에 넣어 사용하면 되지 않을까 생각하여,
Realm 파일을 Bundle에 넣었습니다.
Bundle에 있는 파일을 사용하기 위해서는 다음과 같이 진행해야 합니다: App Targets → Build Phases → Copy Bundle Resources에서 사용하려는 리소스를 추가해 주어야 합니다.
이렇게 사전 준비를 완료하고 아래의 코드를 실행해주면
private func fetchSearchList() {
if let realmPath = Bundle.main.path(forResource: "Exercise", ofType: "realm") {
let realm = try! Realm(fileURL: URL(filePath: realmPath))
list = realm.objects(ExerciseRealm.self).sorted(byKeyPath: "reference", ascending: true)
}
}
짜잔! 아주 실행이 잘 되는 것을 확인해볼 수 있습니다
시뮬레이터에서는 문제없이 잘 돌아가는 모습을 보고 기분좋게 코딩을 하고 있었습니다….
하지만!!
실제 기기에서 실행시 에러 발생!
아래와 같이 Realm 인스턴스를 생성하는데 에러가 발생했습니다.
Thread 1: Fatal error: 'try!' expression unexpectedly raised an error: Error Domain=io.realm Code=3 "Failed to open file at path '/private/var/containers/Bundle/Application/53F5EFA7-872A-4FC0-90A9-0676C81B040B/JimFit.app/Exercise.realm.lock': Operation not permitted" UserInfo={Error Code=3, NSFilePath=/private/var/containers/Bundle/Application/53F5EFA7-872A-4FC0-90A9-0676C81B040B/JimFit.app/Exercise.realm.lock, Error Name=PermissionDenied, NSLocalizedDescription=Failed to open file at path '/private/var/containers/B
시뮬레이터에서는 맥에 있는 파일에 접근하는 것이 가능하지만,
실제 기기에서는 Bundle이 private로 막혀있어서 접근이 불가능하다고 합니다.
아래 Realm 깃허브의 이슈에 들어가보면 저와 같은 이슈를 겪는 사람들이 있었습니다.
https://github.com/realm/realm-swift/issues/4809
에러 원인
앱 번들에 접근하는 것은 보안 및 데이터 무결성을 유지하기 위해
번들은 앱의 private 영역으로 간주되며 앱 외부에서의 직접적인 접근 및 수정이 허용되지 않는다고 합니다
즉, 번들에 있는 파일은 읽기전용이라는 것인데요…
그래서 앱 번들에 있는 Realm 파일은 읽기 전용이며, 앱 실행 중에는 수정할 수 없습니다
그렇다면 Realm
의 인스턴스를 생성하는건 왜 안되는걸까요?
Realm
은 데이터베이스를 나타내며, 데이터의 읽기 및 쓰기를 처리하는 역할하기 때문에
Realm
인스턴스를 생성하는 과정에서는 파일에 대한 읽기 및 쓰기 권한이 모두 포함됩니다.
해결 방법
앱이 실행 중일 때는 앱의 documentDirectory
나 다른 저장소 경로로 Realm 파일을 복사하여 사용할 수 있습니다.
이렇게 복사한 파일을 사용하여 Realm
인스턴스를 생성하면 해당 파일에 대한 읽기 및 쓰기 권한이 부여되어 Realm 데이터를 사용할 수 있습니다
다음과 같이 Appdelegate에서 앱 실행시 documentDirectory
에 파일이 있는지 확인하고 없으면 파일을 복사합니다.
func copyExerciseRealm() {
let fileManager = FileManager.default
// 도큐먼트 디렉토리 경로
let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!
// 저장할 파일의 URL
let fileURL = documentsDirectory.appendingPathComponent("Exercise.realm")
// 이미 파일이 존재하는지 확인
if !fileManager.fileExists(atPath: fileURL.path) {
// 프로젝트 폴더 내의 파일 URL
let bundleURL = Bundle.main.url(forResource: "Exercise", withExtension: "realm")!
do {
// 프로젝트 폴더 내의 파일을 도큐먼트 디렉토리로 복사
try fileManager.copyItem(at: bundleURL, to: fileURL)
} catch {
// 복사 중 에러 발생
print("Error copying file: \(error)")
}
}
}
다음과 같이 Realm의 인스턴스를 만들 뷰컨트롤러에서
documentDirectory
에 접근해 Realm 인스턴스를 생성할 수 있습니다
final class ExerciseSearchViewController: UIViewController {
...
private func fetchSearchList() {
let fileManager = FileManager.default
// 도큐먼트 디렉토리 경로
let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!
// 저장할 파일의 URL
let fileURL = documentsDirectory.appendingPathComponent("Exercise.realm")
let realm = try! Realm(fileURL: fileURL)
list = realm.objects(ExerciseRealm.self).sorted(byKeyPath: "reference", ascending: true)
}
}
Uploaded by N2T