26560


初めてSwiftとRealmで本格的にプログラムを書き、iPhoneアプリをなんとかApp
Storeでリリースできましたが、Realmの処理方法を変更したいと考えています。

マイグレーション処理を挿入し、Xcodeでビルドアップしたところ、こんなエラーが表示されました。

[code]

    Property 'hoge' has been added to latest object model.
    
[/code]

とりあえずは、アプリを丸ごと削除して再ビルドアップすることで問題はクリアできましたが、ふと、思ったのは新バージョンをApp
Storeにアップロードし、ユーザーがアップデートした場合も  
このエラーが発生しないのだろうかということです。

もし自分が考えたようにエラーが出てユーザーがアプリを削除、再インストールするとなると、それまで登録していたデータがなくなるのは目に見えてるのですが、実際の挙動は問題なくアップデートできるのでしょうか？

サンプルのコードです。

[code]

    // DataModel.swift
    import RealmSwift
    class event: Object {
        dynamic var eventID:Int = 0
        dynamic var eventTitle:String?
    
        // 新しく追加するプロパティ
        dynamic var hoge = 0
    
        override static func primaryKey() -> String? {
            return "eventID"
        }
    }
    
    // AppDelegate.swift
    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {        
        let config = Realm.Configuration(
            schemaVersion: 1,
            migrationBlock: { migration, oldSchemaVersion in
                if (oldSchemaVersion < 1) {
                }
        })
        Realm.Configuration.defaultConfiguration = config
    
        return true
    }
    
    func applicationWillEnterForeground(application: UIApplication) {
        NSNotificationCenter.defaultCenter().postNotificationName("appWillEnterForeground", object: nil)
    }
    
    // ViewController
    class ViewController: UIViewController {
        var realmURL:NSURL?
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            setRealmURL()
    
            NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(ViewController.viewWillEnterForeground(_:)), name: "appWillEnterForeground", object: nil)
    
            let realm = try! Realm(fileURL: realmURL!)
            let rlm = event()
            rlm.eventID = 0
            rlm.eventTitle = "First Title"
            try! realm.write {
                realm.add(rlm, update: true)
            }
    
            print(realm.objects(event))
        }
    
        func setRealmURL() {
            var fileName = ""
            let defaults = NSUserDefaults.standardUserDefaults()
            let fileNum = defaults.integerForKey("realmFileNum")
    
            if fileNum == 0 {
                fileName = "f0.realm"
            } else {
                fileName = "f1.realm"
            }
    
            var config = Realm.Configuration()
            config.fileURL = config.fileURL!.URLByDeletingLastPathComponent?.URLByAppendingPathComponent(fileName)
            Realm.Configuration.defaultConfiguration = config
    
            realmURL = config.fileURL!
        }
    
        func compactionFile(URL: NSURL) {
            let oldRealmURL = URL
            var newRealmURL:NSURL!
    
            let defaults = NSUserDefaults.standardUserDefaults()
            let fileNum = defaults.integerForKey("realmFileNum")
            if fileNum == 0 {
                newRealmURL = oldRealmURL.URLByDeletingLastPathComponent!.URLByAppendingPathComponent("f1.realm")
                defaults.setInteger(1, forKey: "realmFileNum")
            } else {
                newRealmURL = oldRealmURL.URLByDeletingLastPathComponent!.URLByAppendingPathComponent("f0.realm")
                defaults.setInteger(0, forKey: "realmFileNum")
            }
    
            let realm = try! Realm(fileURL: oldRealmURL)
    
            do {
                try realm.writeCopyToURL(newRealmURL)
            } catch {
            }
    
            var config = Realm.Configuration()
            config.fileURL = newRealmURL
            Realm.Configuration.defaultConfiguration = config
    
            try! NSFileManager.defaultManager().removeItemAtURL(oldRealmURL)
        }
    
        func viewWillEnterForeground(notification: NSNotification?) {
            compactionFile(realmURL!)
            setRealmURL()
            print("realmURL = ",realmURL)
            print("schemaVersionAtURL = ",try! schemaVersionAtURL(realmURL!))
        }
    
    }
    
[/code]


CC BY-SA 3.0
2016-06-07T01:15:53.617
26520
2016-06-08T07:52:32.213
2016-06-07T23:31:43.347
15155
15155
question
realm
Realmのマイグレーション後のApp Storeでのアップデートの挙動について

[code]

    Property 'hoge' has been added to latest object model.
    
[/code]

上記のメッセージはモデルの定義を変更したのにもかかわらず、マイグレーションが正しく行われていないことを示しています。  
モデルの定義を変更した場合に、マイグレーションを行うことはプログラマの責任ですので、エラーとして例外が発生し、アプリケーションはクラッシュします。

実際の挙動としては、アプリケーションをアップデートしたら起動時にクラッシュするようになってしまうので、ユーザーがアプリケーションをいったん消して、再インストールしてくれれば、古いデータが消えるので新しいバージョンをまた使えるようになりますが、起動しなくなったことで、アプリを削除して使わなくなってしまう恐れもあります。

正しい実装としては、エラーが起こらないように修正することです。

マイグレーション処理を書いたということですが、それが間違っています。  
スキーマバージョンを上げるのを忘れているはずです。

その辺りのコードを見せてもらえますか？

Realmでデータモデルを変更した場合は、1.スキーマバージョンを前回より大きい値に増加させる、2.（必要に応じて）データ移行のためマイグレーション処理を書く、という処理が必要です。

Realm Swift/Objective-
Cにはオートマイグレーションがあるので、プロパティを統合・分割するなどがなく、単なるプロパティの追加・削除の場合は2は必要ありません。単にスキーマバージョンを上げるだけでOKです。


CC BY-SA 3.0
2016-06-07T01:53:35.287
26522
2016-06-07T01:53:35.287
5519
26520
answer


いくつかRealmとiOSのAPIを勘違いされているところがありましたので修正したコードとともに解説します。

まず、Realmファイルのコンパクションを起動中に何度も行うのは、正しく実行するのが難しいので起動時に１度だけにしてください。

Realmのファイルをコピーして再読み込みするのはRealmのインスタンスが全てのスレッドで全て解放されていることを保証する必要があります。それを起動中に行うのは大変です。

コンパクションの処理を少し直して、AppDelegateで起動時に１度だけ呼ぶようにしました。ご覧ください。

また、元の処理では、`Realm.Configuration`を何度も新しく作って、その度に`Realm.Configuration.defaultConfiguration`に代入していますが、これは間違いです。

`Realm.Configuration.defaultConfiguration`に代入すると、それが既定の設定になり、`try!
Realm()`を呼んだ時にはいつでも`defaultConfiguration`が使われるようになります。  
そのことにより、何度も同じ設定をしたり、`Configuration`オブジェクトを渡して回らなくて済む、というものです。

元のコードではいろいろなメソッドを呼ぶたびに新しく`Configuration`が作られて、`defaultConfiguration`に代入されているので、その度に`try!
Realm()`で使われる設定が変わってしまいます。  
マイグレーションが正しく行われなかったりするのはそのためです。デフォルトの設定がコロコロ変わっているので、Realmをインスタンス化するタイミングによって、どのファイルが使われるのかが変わっているためです。

`defaultConfiguration`も基本的には最初に１度だけ設定して、あとは単に`try!
Realm()`を呼ぶことで全て同じ設定を使えるようにするのが適切ですので、そのように変更しました。

最後に、これはRealmと関係ないですが、iOSは標準で、`UIApplicationWillEnterForegroundNotification`という通知を送信しています。そのため、わざわざ`AppDelegate`の`applicationWillEnterForeground()`メソッドで通知を送り直す必要はありません。ただ`UIApplicationWillEnterForegroundNotification`の通知を受ければいいです。

### AppDelegate.swift

[code]

    func setupRealm() {
        let defaultConfig = Realm.Configuration.defaultConfiguration
    
        let realmFileURL = defaultConfig.fileURL!.URLByDeletingLastPathComponent!.URLByAppendingPathComponent("f0.realm")
    
        var config = defaultConfig
        config.fileURL = realmFileURL
        Realm.Configuration.defaultConfiguration = config
    
        setupMigration()
        compaction()
    }
    
    func setupMigration() {
        var config = Realm.Configuration.defaultConfiguration
        config.schemaVersion = 1
        config.migrationBlock = { migration, oldSchemaVersion in
            if (oldSchemaVersion < 1) {
    
            }
        }
        Realm.Configuration.defaultConfiguration = config
    }
    
    func compaction() {
        let config = Realm.Configuration.defaultConfiguration
    
        let realmFileURL = config.fileURL!
        let copyFileURL = realmFileURL.URLByDeletingLastPathComponent!.URLByAppendingPathComponent("f1.realm")
    
        let fileManager = NSFileManager()
        if fileManager.fileExistsAtPath(realmFileURL.path!) {
            autoreleasepool {
                do {
                    let realm = try Realm(configuration: config)
                    try realm.writeCopyToURL(copyFileURL)
                } catch {
                    let _ = try? fileManager.removeItemAtURL(realmFileURL)
                }
            }
    
            let _ = try? fileManager.removeItemAtURL(realmFileURL)
            let _ = try? fileManager.moveItemAtURL(copyFileURL, toURL: realmFileURL)
        }
    }
    
    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        setupRealm()
        return true
    }
    
[/code]

### ViewController

[code]

    class ViewController: UIViewController {
        var realmURL:NSURL?
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            // UIApplicationは標準で
            // UIApplicationWillEnterForegroundNotificationなどの
            // 通知を送信している
            NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(ViewController.viewWillEnterForeground(_:)), name: UIApplicationWillEnterForegroundNotification, object: nil)
    
            let realm = try! Realm()
    
            let rlm = event()
            rlm.eventID = 0
            rlm.eventTitle = "First Title"
            try! realm.write {
                realm.add(rlm, update: true)
            }
    
            print(realm.objects(event))
        }
    
        func viewWillEnterForeground(notification: NSNotification?) {
            // コンパクションをアプリケーションの動作中に
            // 正しく行うのは難しいのでは起動時に１度だけにする
        }
    }
    
[/code]


CC BY-SA 3.0
2016-06-08T07:52:32.213
26560
2016-06-08T07:52:32.213
5519
26520
answer