LoginSignup
6

More than 3 years have passed since last update.

iOS13のUserDefaultsにつまずいた

Last updated at Posted at 2019-12-12

はじめに

もうネタがない...と思って色々漁っていたところ下記のようなものを見つけました:joy_cat:

iOS 13 - Attempting to store >= 4194304 bytes of data in CFPreferences/NSUserDefaults on this platform is invalid

iOS13 で UserDefaults に 4194304 bytes 以上のデータを保存できない模様:exclamation::question:

検証

とりあえず検証してみました。

方法

  1. Macでスクショを撮る(画面サイズのやつ(13328104 bytes))
  2. Xcode のプロジェクトに追加する
  3. UserDefaults に画像を保存する(処理は下記)
let url = Bundle.main.url(forResource: "test", withExtension: "png")!
UserDefaults.standard.set(try? Data(contentsOf: url), forKey: "image")

結果

iOS13

コンソールに下記の警告が出ました。

[User Defaults] CFPrefsPlistSource<0x6000024e0800> (Domain: アプリ, User: kCFPreferencesCurrentUser, ByHost: No, Container: (null), Contents Need Refresh: No): Attempting to store >= 4194304 bytes of data in CFPreferences/NSUserDefaults on this platform is invalid. This is a bug in アプリ or a library it uses

[User Defaults] CFPrefsPlistSource<0x6000024e0800> (Domain: アプリ, User: kCFPreferencesCurrentUser, ByHost: No, Container: (null), Contents Need Refresh: No): Transitioning into direct mode

コンソールに何か出ましたが、下記の処理でデータは取れてるようでした。

if let data = UserDefaults.standard.data(forKey: "image") {
  print(data.count)
  imageView.image = UIImage(data: data)
}

print の表示も 13328104 なのでサイズはそのままでした。アプリを再起動しても取得できました。

その後、アプリを再起動して下記の処理を書くと再度コンソールに警告が出ました。

UserDefaults.standard.set("しんぐるとん", forKey: "Singleton")

[User Defaults] CFPrefsPlistSource<0x600000df4480> (Domain: アプリ, User: kCFPreferencesCurrentUser, ByHost: No, Container: (null), Contents Need Refresh: Yes): Attempting to store >= 4194304 bytes of data in CFPreferences/NSUserDefaults on this platform is invalid. This is a bug in アプリ or a library it uses

[User Defaults] CFPrefsPlistSource<0x600000df4480> (Domain: アプリ, User: kCFPreferencesCurrentUser, ByHost: No, Container: (null), Contents Need Refresh: No): Transitioning into direct mode

微妙に違う?? Contents Need Refresh: Yes Yes になってる??

画像データも文字列も UserDefaults から取得することができました.

iOS12.4.1

コンソールには何も表示されることなく読み書きできました:tada:

4194304 byte とは

4194304 byte ってどれくらい保存できるんだ?ってことなんですが下記のような struct を作って保存すると(文字数にもよりますが...)49 byte でした。

let user = User(userID: "1234567890", userName: "SingleSingle") // 49 byte
UserDefaults.standard.write(user, key: .user)

struct User: Codable {
    let userID: String
    let userName: String
}

extension UserDefaults {
    enum Key: String {
        case user
    }

    @discardableResult
    func write<T: Codable>(_ value: T?, key: Key) -> Bool {
        let data = try? JSONEncoder().encode(value)
        set(data, forKey: key.rawValue)
        return synchronize()
    }

    func read<T: Codable>(_ type: T.Type, key: Key) -> T? {
        guard let data = data(forKey: key.rawValue) else {
            return nil
        }
        return try? JSONDecoder().decode(type, from: data)
    }
}

だいたい 85598 件くらい上記のデータが保存できるみたいです(十分な容量がありそう)。

関係ないですが syncronize()公式ドキュメント

this method is unnecessary and shouldn't be used.

とあるので必要なさそうですが担当のアプリでこれがないとバグったのでとりあえずまだ入れてます。

結論

ちょっと調べてみましたが公式のドキュメントは見当たりませんでした...

とりあえずは iOS12 でも iOS13 でも UserDefaults で 4194304 bytes 以上のデータの読み書きはできるようでした。

しかし

Attempting to store >= 4194304 bytes of data in CFPreferences/NSUserDefaults on this platform is invalid.

という警告が出てる以上このまま使うのは良くない:neutral_face:

大きなデータは他の方法で保存するよう修正した方がいいでしょう:punch:

下記のようにとにかく UserDefaults 以外の保存方法に書き換えるしかなさそう??

さいごに

そもそも UserDefaults にそんなでかいデータ入れる実装ってどうなんだ?というのはあります:expressionless:

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6