ios12でCoreDataのExternal Storageを利用しているデータが破損してしまう問題が発生した件

どうも。Reoです。

昨日、Apple Developerの更新をしないといけないのを思い出し、1ヶ月ぶりくらいにAppStore Connectをチェックしてみたところ、RobinでiOS12利用時にクラッシュが多発していることが発覚しました。

AppStoreもチェックしてみると、落ちるようになったからアップデートしてくれというレビューが数件きていました。

慌てて調査したところ、今回の表題の件にぶち当たりました。

残念ながら解決はしていません。行き詰まってしまったので調査した件についてまとめておこうと思います。

 

発生現場

iOS12.0.1にて発生確認。

CoreDataを利用していて、Allows External Storageにチェックを入れている場合に発生。

Allows External Storageにチェックを入れている場合でも、容量が小さい等で外部ストレージが使われていなければ発生はしていません。

 

発生現象

まずどういうことが起きているかというと、単直に言えばiOS12にアップデートするとデータが破損してしまいます。

Robinでは、写真全般をこの方法で保存していました。

この画面では、オリジナルの画像を読み込んで加工して配置していたので、画像が失われて表示されなくなってしまっています。

 

お次はこちらの画面。

このサムネイル画像は別物として保存していて、かつ容量が小さくExternal Storageにチェックを入れていても利用されていないので画像は失われていません。

しかしこの画面からオリジナルの画像を表示しようとすると落ちます。

本来ならこの画面が見られるようになっています。

この画面を表示しようとすると落ちちゃうわけです。

あとRobinではカメラ起動時に前回撮った写真を表示させているので、カメラすらも起動ができません。

 

ios12で初めて起動した時にはデータは失われておらず、アルバムを開いて少しすると消えています。ソースがどれだったか忘れてしまったんですがcontextか何かが2回ほど呼ばれると消えるようです。

なので私のアプリではアルバムを開くとデータが消える地獄のような状態になっています。

 

フォルダ内にデータ自体は残っている

MacにiPhoneを繋いで、実際にアプリのフォルダを覗いてみたところ、写真データ自体はちゃんと残っています。

DB Brouser for SQLiteでデータベースもみてみました。

 

ちゃんと全部入っている。しかし実際にこのデータを取得するとnilが返されてしまいます。

 

SQLiteで直接アクセスしてみた(ダメだった)

CoreDataからアクセスしようとするとnilが返されてしまうので、SQLiteで直接アクセスしてみました。

結果から言うとアクセス自体はできるが、ダメでした。


import SQLite

// Data取得できなかった時だけ
if let path = AppDelegate().applicationDocumentsDirectory.appendingPathComponent("SingleViewCoreData.sqlite")?.absoluteString {
    
    do {
        let db = try Connection(path, readonly: true)
        
        for row in try db.prepare("SELECT ZPHOTO,ZIMAGEID ZIMAGEID FROM ZIMAGE") {
            guard let blob = row[0] as? Blob,
                let imageID = row[1] as? String
                else{
                print("Blobに変換できない")
                return
            }
            
            // 一致するImageIDがあればDataを取得
            if imageID == fetchData[0].imageID! {
                guard let string = String(bytes: blob.bytes, encoding: .utf8) else {
                    print("stringにできない")
                    return
                }
                guard let filePath:URL = AppDelegate().applicationDocumentsDirectory.appendingPathComponent(".SingleViewCoreData_SUPPORT/_EXTERNAL_DATA/" + string) else {
                    return
                }
                // ファイルパスは正しい filePath is correct.
                do {
                    // このdataが欲しい
                    let data = try Data(contentsOf: filePath)
                } catch {
                    // しかしデータはない。 but there is no data.
                    print("error\(error)")
                }
            }
        }
    }catch {
        print(error)
    }
}

こんな感じのコードを半日ぐらい考えて書きました。Binding型をどうDataにすればいいのかで迷ってようやく辿り着いたんですが、結果はダメでした。

面白いことに、filePath自体はあるんですが、let data = try Data(contentsOf: filePath)のところでdataがないよってエラーが出ます。あるんじゃねーのかよちくしょー(´;ω;`)

 

安易にテーブルの一番上のデータが_EXTERNAL_DATA内に含まれているので、全てあるものだとこの方法をとりましたが、実際は違いました。

 

ないデータがはっきりしたところで改めて比較してみると、

データが破損しているもの

 

というわけです。そもそもデータが変わっちゃっています。オワコンですわー。

 

iOS12.1 beta3で直るとの噂

調査中にiOS12.1 beta3で直ると言う噂を聞き、祈る思いでベータ版を入れてみました。

Glad i found this thread, thought i was going mad.

Same issue for our app, bit of a major disaster, eventually had to switch off external storage as quick fix.

Will test iOS12.1 beta 3 later, but too many unrecoverable hours lost debuggin this problem, thinking it was in the code.

https://forums.developer.apple.com/thread/109189

 

結果。

おそらくバグ自体は直っています。データが失われることはなくなりました。

ただ、失われたデータが戻ってくることはない。

 

解決法探しています

iOS12になってもう1ヶ月くらい経ちましたが、未だ日本語でこのことに触れている人がいないところをみると、誰もCoreDataなんて使ってねーなって感じがしています。

なぜ自分はCoreData使っていたのか、なぜApple純正のもの使ってる方がこんな仕打ちにあうのか。

おかげさまでRobinは完全にオワコンなものになってしまいました。

 

でもiOS12になって1ヶ月気づかずに放置していた方も悪いんですけどね。いや、むしろ早く気づいても情報がなさすぎて迷宮入りしてしまっていたからもはや変わらんですわ・・・。

iOS12に対応してくださいって言われてますが違うんです。iOSが対応しろ案件です。

 

Allows External Storage のチェックを外す方法を提案されているところもありましたが、自分はマイグレーションに失敗して諦めています。

ただdb見る限り、対応しているはずのものが対応しなくなっているのでマイグレーション無理なのでは?と考えています。

 

有力参考URLはこちら2つです。

https://forums.developer.apple.com/thread/109189

https://stackoverflow.com/questions/52586564/ios-12-specific-problem-core-data-external-storage-binary-data-corruption

 

どちらも解決したみたいに書かれてますが、解決しません。

 

ここからアプリを救う方法があるのか。

最愛の子の写真を毎日撮っていた人がいたらと思うと、Appleのせいでデータ飛んだけど許してで済まないですよ。訴えられてもいいぐらいのレベルじゃないですか。

そうなったら私はAppleを訴えるしかないわけなんですが、そんなことできるわけもなく、ひっそりと生を終えるしかないですね。

 

このまま諦めてアプリを捨て、ユーザがせっかく撮りためてきたデータが無くなってしまうのが一番最悪の事態なので、少し時間がかかっても利用できるようにアップデートはしないとなと思っています。

とりあえず今は、ユーザに失われたデータの紐付けを委ねる実装をしようかなと考えています。それしか方法が思いつかないですし。

 

しかしなんでこんなことになってしまったのか。なんでこんな目に合わないといけないんだ・・・。

記事書いたはいいけれど、同じ現象でこの記事にたどり着く人がなるべく少なくありますように祈っています。

みんなCoreData使ってないって信じてる。

 

ではでは。

 

 

 

iOS

Comments...

コメントは認証制です。詳しくは下記の注意をお読みください。お気軽にコメントお願いします!

Write a Comment

コメント時の注意

「Twitter」「Facebook」「Google+」「WordPress」のいずれかのアカウントをお持ちの方は各アカウントと連携することでコメントできます。 コメントしたことはSNSに流れませんので、アカウントをお持ちの方はこちらの方法でコメントを投稿して下さると嬉しいです。 アカウントをお持ちでない方はメールアドレスで投稿することができます。 初回コメント時は承認後に表示されます。

Related Memo...

記事を書くほどでもないけれどメモっておきたいこと

テスト投稿。

例えばiphone7 の画面サイズ

750 × 1334
半分375 × 667

iOS

UINavigationController + UIScrollView の組み合わせで使っている時に謎の余白ができる時

UINavigationController + UIScrollView の組み合わせで使っていて、UIScrollView 上に AutoLayout で上下左右0で View を設置しているのに、30px程度上にずれてしまうとき。

`navigationController.navigationBar.isTranslucent = false` にすると直るかもしれない。

ScrollView上のコンテンツとNavigationBarの重なっているところが透過していたら多分これで直せるはず。

通常のターゲットではちゃんと動いているのに、iOSSnapshotTestCase を用いたテストでだけこの対応が必要なのよくわからないけれど。。。

iOS

UITableView.RowAnimation の .none はアニメーションするよ

UITableView.RowAnimation の .none はアニメーションがnoneなわけじゃなく、デフォルトの設定を使うよという意味らしい。

The inserted or deleted rows use the default animations.

なのでアニメーションしちゃう。今更の気づき。

 

iOS
more