본문 바로가기
Swift

[Swift] Realm 파일을 프로젝트 내부에 삽입하는 방법 및 사용하기 (파일 접근 에러)

by Jimmy_iOS 2023. 10. 1.

요약

실제 기기에서는 번들에 있는 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