質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.38%

  • Xcode

    5157questions

    Xcodeはソフトウェア開発のための、Appleの統合開発環境です。Mac OSXに付随するかたちで配布されています。

  • Objective-C

    1279questions

    Objective-Cはオブジェクト指向型のプログラミング言語のひとつです。C言語をベースにSmalltalkが取り入れられています。

UserDefaultsが読み込めない(かもしれない)挙動について

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 1,568

takaki.satou

score 15

UserDefaultsでのデータの取り扱いについて問題があり、皆さまのお知恵を拝借したいと思います。
わかる限りの情報を書き連ねるため長文となりますが、ぜひよろしくお願いいたします。

X-Codeでアプリを開発していますが、リリース環境にてUserDefaultsに保存しているデータが「【読みだせなかった】ような挙動」を見せています。
ユーザー様からのご指摘で発覚し数回のアップデートを行いましたが、どうやら見当違いの対策ばかりをやっていたようです。

しかもテスト環境(X-code、実機テスト)では再現できず、根本原因に至っていません。
結果、現状の把握や解決策の糸口が、全く見当もつかない状態です。

想定している動き

まず、仕様(というか期待する動き)は下記のとおりです。

  • サーバ(DB)との通信でKeyとなる文字列を生成(毎起動時)
  • このKeyを用いて、DBにユーザーデータをバックアップしたり、読み出したりする。
  • [defaults registerDefaults:dict]で保存。既にデータがある場合は更新されない(はず)。
  • アプリを完全に削除でもしない限り、この値は永続的に使用できる(はず)。

問題の動き

問題の現象は以下の通りです。

  • Keyにしている文字列が、意図せず変更されている
     (同一ユーザーからのFeedbackで確認。不具合が起こるたびに、Keyが変わっている)。
  • DBでKeyが参照できなかったため、新規ユーザーとして認識し新規レコード作成。
      データは初期化してしまう。
  • この現象はアップデート時に頻発(特に一部ユーザーにて)、アップデート時以外にも、発生していると思われる(Feedbackの内容より)。

我ながら粗い仕様だと承知しております。
しかし、UserDefaultsのKeyが読み込めていない(であろう)状態では、アップデートの度にデータが消失してしまう危険性が残ってしまいます。

私自身、X-codeを触り始めたばかりなので、根本的な勘違い・見当違いをしているのかもしれません。
似たような現象の起こった方、問題が推測できる方、何かしらの糸口となるヒントでも構いません、アドバイスを頂けないでしょうか?

よろしくお願いいたします。

※補足※
別質問にて、端末側でkeyを生成する危険性についてはご指摘を頂き、サーバにて生成を行うよう、現在対応中です。

【2016/02/04追記】
依頼により追記いたします。

>[defaults registerDefaults:dict]で保存。
Keyとして生成された文字列を、Dictionary型 dictに代入後、上記の記述によりdefaults(NSUserDefaults)に保存(というより初期値設定)しています。

>既にデータがある場合は更新されない(はず) その根拠は?
上記の記述が「初期値設定」であり、前回の起動で初期値が設定されていた場合は、何も起こらないものだと認識しておりました。
(少なくとも、私が引き継いだ段階では上記の認識で組まれており、私もその認識で理解した次第です)

毎起動時に新規にKeyを生成しているんですか?
KeyにはUUID(identifierForVendor)を流用しています。これも引継ぎ時には既にあった記述ですが、前後の記述を見る限り『毎回記述をする必要性』は無いと感じています。

>アプリを完全に削除でもしない限り、この値は永続的に使用できる(はず) その根拠は?
こちらも「そういうものだ」という認識でした(引継ぎ時の組み方から判断して)。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • Stripe

    2016/02/03 23:22

    毎起動時に新規にKeyを生成しているんですか?

    キャンセル

  • Stripe

    2016/02/03 23:23

    >アプリを完全に削除でもしない限り、この値は永続的に使用できる(はず) その根拠は?

    キャンセル

  • takaki.satou

    2016/02/04 19:13

    追加・修正依頼について、記事内に追記させていただきました。ご確認くださいませ。

    キャンセル

回答 1

checkベストアンサー

0

今回の質問の説明はStripeさんがコメントされているように謎だらけなのですが、前回の質問の説明と合わせて読んで、なんとか理解できたので気になった点をコメントします。

まず今回の質問の説明で「[defaults registerDefaults:dict]で保存」と書かれていますが、registerDefaultsは、指定のキーにデータを保存する機能ではなく、指定のキーにデータがない場合の初期値を設定する機能です。
データを保存するには[defaults setObject:@"data" forKey:@"key"]を使います。
もし誤解されていたのであれば、NSUserDefaultsの仕様を再確認した上でコードを再検証することをお勧めします。

次に前回の質問ですが、UUIDというのは確かに全ての端末で重複しないことを保証している訳ではないのですが、重複することはないという前提で設計しても問題ないくらいの重複率となるよう設計されたIDです。
(参考)
http://d.hatena.ne.jp/satosystems/20140123/1390460471

次にidentifierForVendorというのは、絶対変わらないのではなく、同一ベンダーのアプリを全て削除した後再インストールしたら変化することを理解していますか?(再インストールではなくアップデートであれば変化しません)
(参考)
http://qiita.com/ashdik/items/1ecbcbddf6ae476bd253#2位-uidevice-currentdeviceidentifierforvendor-uuidstring

この仕様から、自社アプリを複数インストールしているテスト端末では、テストアプリを何度再インストールしてもidentifierForVendorは変わらず、その会社のアプリを1個しかインストールしてない一般ユーザーは再インストールによりidentifierForVendorが変わるということになります。
また、参考URLに書かれているように、すぐに再インストールした場合は変わらない場合もあるようです。
こういう事態が発生している可能性はないですか?
ちなみに、前回の質問のコメントで「違うUUIDが返ってきたとしても、UserDefaultsに保存されたデータを選択する、という動きをしているはずです」と書かれていますが、アプリを再インストールしてidentifierForVendorが変化した場合はUserDefaultsに保存したデータも初期化されていますが、それでちゃんと動作できますか?

最後に、(AppStoreのバグにより)アプリをアップデートしただけで変化する場合も過去にはあったようなので、
https://teratail.com/questions/24053
を参考に見ておいてください。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/02/04 19:13

    TakeOneさま、ありがとうございます。
    ご指摘の通りあまりにも把握できておらず、また乱文にて大変失礼いたしました。
    ご丁寧にご指摘・ご指導いただき、感謝しております。

    ご質問・ご指摘頂いた点、回答です。
    ※質問にも追記させていただいておりますので、併せてご覧くださいませ。

    > 指定のキーにデータがない場合の初期値を設定する機能です。
    説明が明確ではありませんでした。私も同じように認識しておりました。

    > こういう事態が発生している可能性はないですか?
    ユーザー様が削除後、再インストールされるケースは確かにあると思います。また、その場合はデータが消えてしかるべきなので、それで良いのですが…。
    同一Venderアプリが1つのみの挙動については、把握はしていましたが、実際ご指摘されるまで、その環境を再現したテストをしておりませんでした。
    試してみたいと思います。

    > UserDefaultsに保存されたデータを選択する、という動きをしているはずです
    こちらは「もしかしたらアップデートでもUUIDが変わることがあるんじゃないか」という疑問に基づくものです。ご指摘のApple社のバグにより、それが発生することがある、ということも把握しておりました。

    しかし、数回同じようなFeedbackを頂く方もいらっしゃいます。前回のアップデートは10月で、今回1月にもアップデートしています。Apple社のバグはすでに影響範囲外かと思いました。

    取り急ぎ、ご確認いただいたところの、回答をさせていただきます。
    お忙しい中ありがとうございました。

    キャンセル

  • 2016/02/04 23:23 編集

    返信ありがとうございます。一点だけとても気になる点があります。

    >>[defaults registerDefaults:dict]で保存。
    >Keyとして生成された文字列を、Dictionary型 dictに代入後、上記の記述によりdefaults(NSUserDefaults)に保存(というより初期値設定)しています。

    相変わらずregisterDefaultsで「保存」しているように書かれているのがとても気になります。
    registerDefaultsは保存ではない(NSDefaultsファイルの内容を何も書き換えない)ということを理解されていますか?
    registerDefaultsはファイルの書き換えは何もせず、ファイルに指定キーのデータがなかった時のstringForKey返却値(初期値)を設定しているだけです。
    例えば、
    1.identifierForVendorでUUIDを取得
    2.そのUUIDをregisterDefaultsで登録
    3.stringForKeyでUUIDを読み込んで使用
    としている場合、
    アプリを再起動後、仮にidentifierForVendor返却値が変化した場合、
    ファイルにUUIDを保存しているので、使用するUUID(stringForKeyが返却する値)は前回(最初に)取得したUUIDとなるよう設計されているように聞こえるのですが、実際にはファイルは何も保存されていないので、この場合、使用するIDは新しいUUIDに変わります。
    本来は
    1.stringForKeyでUUID取得
    2.データなし(nil)だったら、identifierForVendorでUUIDを取得し、それをsetObject:forKeyで保存
    3.取得したUUIDを使用
    とすべき処理のように思えますがいかがでしょうか?

    この方法でやるなら、そもそもidentifierForVendorのようにUUIDがベンダー内で一定であることを求める必要もないので、[NSUUID UUID]でUUIDを取得してもよいと思います。

    キャンセル

  • 2016/02/05 22:21

    TakeOneさま
    ご返答頂き、ありがとうございました。

    再びご指摘の点を確認させていただき、ようやく原因の特定に至りました。
    やはり再三のご指摘の通り、「registerDefaults」を『保存している』と誤解している所に問題があったようです。
    リリースVer.のUserDefaultsには、KeyとなるべきIDが保存されておりませんでした。
    ご提案の処理についてもごもっともでございます。
    早速、そのように処理を変更させていただきました。(←UserDefaultsからデータが正しく読み出せました!!)
    [NSUUID UUID]についても確かにその通りなのですが、今回不具合報告が一部に限られていたのも、複数アプリをインストールして頂いていたのでは、と考えています。なので、少なくとも次のアップデートまでは(この不具合を収拾するまでは)ForVenderを使用したいと思います。

    見返せば、最初から丁寧にご指摘頂いていたのに(しかもすごく初歩的な点を…)、自分の理解を優先する傲慢さに恥じ入るばかりでございます…。

    ともあれ、これで不具合(というより仕様不備)は解決できそうです!
    ありがとうございました!!!

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.38%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る

  • Xcode

    5157questions

    Xcodeはソフトウェア開発のための、Appleの統合開発環境です。Mac OSXに付随するかたちで配布されています。

  • Objective-C

    1279questions

    Objective-Cはオブジェクト指向型のプログラミング言語のひとつです。C言語をベースにSmalltalkが取り入れられています。