徹底的にユーザ辞書を同期してみる

MacBookで「まーゔぇりくす=Mavericks」と登録したはずなのに、iPhoneで「まーゔぇりくす」と入力しても期待する「Mavericks」が表示されない...。いつから同期していなかったのかは不明だが、iOS7とOSX 10.9 Mavericksの環境で、ユーザ辞書が同期していないことに気付いた。

またいつものバージョンアップに伴う不具合か...と思ってしばらく放っておいたのだけど、OSX 10.9.1になっても自分のユーザ辞書は一向に同期する気配がない。どうなっているのかと検索してみると、何と!ちゃんと同期している方もいるらしい。

本当に同期するのか?こうゆうことは自分で試してみなければ気が済まない。やってみた。

検証

  • iCloudに接続するすべての端末で、一旦「書類とデータ」の同期をオフにした。
  • その後、SafariからiCloudのWebページにログインして、
  • 右上の名前のリンクから、アカウント設定 >> 詳細設定 >> 書類とデータのリセットを実行した。
  • MacBookのユーザ辞書には「まーゔぇりくす=Mavericks」のみの状態にして、「書類とデータ」の同期をオン。
ストアカウントからiCloudに接続してみる
  • ストアカウントにログインして、そこからiCloudにサインインしてみた。
  • ユーザ辞書を確認すると、いつものアカウントで登録した「まーゔぇりくす=Mavericks」が表示されている。
  • 逆に、ゲストアカウントで「れてぃな=Retina」を登録すると、いつものアカウントでも同じ辞書が確認できた。

素晴らしい!OSX環境ではちゃんと同期しているのだ。

iPhoneを工場出荷状態に戻す
  • iTunesiPhoneを接続して、今すぐ「バックアップ」を実行。
    • バックアップ容量を節約するため、iPhotoと同期して、カメラロールを空っぽにしておいた。
    • バックアップさえちゃんとしておけば、iPhoneは元どおりに復元できるはず。
    • 念には念を入れて、TimeMachineによるバックアップもしておいた。
    • その後、恐れずに「iPhoneを復元...」ボタンを押してみる。
  • iPhoneを復元...」処理が進むと、二つの選択を迫られた。
    • 新しいiPhoneとして設定するのか?
    • 既存のバックアップから復元するのか?
  • 工場出荷状態に戻したいので、新しいiPhoneとして設定してみた。
  • 処理が進んで、iCloudの設定をして、待つこと暫し...
  • 驚いたことに「まーゔぇりくす=Mavericks」と「れてぃな=Retina」が確認できる!

素晴らしい!工場出荷状態のiPhoneならちゃんと同期するのだ!

iPhoneを復元してみる
  • なんだ、iCloudはちゃんとできる子だったんだ!と気を良くして「バックアップを復元」を実行してみた。
  • するとiPhoneのユーザ辞書から「Mavericks」と「Retina」が消えた...。(ユーザ辞書には何も表示されない)

これで確信した。同期できないのは、iPhone中のファイルの問題であると。

  • iCloudの「書類とデータ」の同期をオフにすると、ユーザ辞書にかなりの数の単語が表示された。
  • ユーザ辞書が大き過ぎて、うまく同期できない原因になっているのかもしれない。
  • そう思って、1つの単語を削除しようとすると、完了するまでに30秒以上かかる。
  • ユーザ辞書の単語登録が多過ぎるためか、辞書の編集をすると、操作がやけに重い。

いつの間にか膨れ上がってしまったiPhoneのユーザ辞書が、かなり怪しい!

iPhoneのユーザ辞書はリセットできるのか?

  • 工場出荷状態にすると、iPhoneのユーザ辞書はリセットされているはず。
    • リセット=初期状態で何も登録がない状態。
  • しかし、工場出荷状態ではその他の設定もすべてリセットされており、使い慣れたiPhoneにはほど遠い状態である。
  • そこで、復元によって以前の状態に戻すのだけど、そのタイミングでユーザ辞書も多過ぎる単語で溢れてしまう...。
  • ユーザ辞書を1語ずつ削除しようとしてみたが、1件30秒以上かかると、すべて削除するのはいつになるか見当もつかない...。

ユーザ辞書だけ復元したくないのだけど、脱獄しない限り、そんなことはできそうにない?

iPhoneのバックアップの中身を覗く

1つだけ思い当たる方法があった。

  • iOS Filesを選択して、Extractボタンを押すと、iOS Filesのファイルとディレクトリ構造が書き出された。

  • そしてiOS Files/Library/Keyboard/の奥深くに潜む、いくつかのCloudUserDictionary.sqliteがユーザ辞書の実体ではないかと予想した。
  • 当時プロファイルを削除した時の方法は、復元されたiOS Files中のCloudUserDictionary.sqliteのバイト数を調べて、
  • 同じサイズのファイルを~/Library/Application Support/MobileSync/Backup/の中から見つけ出し、削除していた。
  • 過去の成功体験から、今回も同じ方法でやってみと...

  • ガーン、復元できないと警告された。
  • 以前も似たような警告をされたのだけど、その時は目指すファイルがちゃんと削除れた状態で復元され、目的は達成できた。
  • しかし、今回は本当に復元できていないようだ。ユーザ辞書の内容を確認してみると、以前と変わらず。同期もしない...。

iOSバージョンアップの過程で、1つでも復元できないファイルがあると、復元処理全体がキャンセルされることになってしまったのか?

バックアップの仕組みを探る

  • もはやこれまでか...と思いながら、バックアップファイルの中をブラウズしていると、1つの仕組みに気付いた。
  • バックアップファイルは、以下の構造になっている。
    • 多数のハッシュ値のファイル
    • Info.plist、Manifest.mbdb、Manifest.plist、Status.plist
  • ハッシュ値のファイルは、iOS中に存在するひとつ一つの、ファイルそのものである。
  • ファイル名がハッシュ値に変換されたファイルとして、バックアップされているのだ。
$ ruby -r digest/sha1 -e 'puts Digest::SHA1.hexdigest("HomeDomain-Library/Keyboard/dynamic-text.dat")'
0b68edc697a550c9b977b77cd012fa9a0557dfcb
  • バックアップを暗号化していなければ、Quick Lookで見られるファイルも多い。
  • 一方、.plistや.mbdbファイルは、ハッシュ値のファイルを管理する情報である。
  • ハッシュ値のファイルに必要な付属情報紐づけて、管理しているのだ。
  • 中でもManifest.mbdbには、本の「目次」に相当する役割がありそう。
    • iOSの設定ファイルらしきディレクトリ情報は、このManifest.mbdbにしか存在しなかったので。
  • バイナリファイルなのだけど、hexdump -Cコマンドで確認してみると、ディレクトリ情報が確認できた。
$ hexdump -C ~/Library/Application\ Support/MobileSync/Backup/fc4dd318ecd23105d1fde5b5f6c782ec4c2504e2/Manifest.mbdb | less

...(中略)...
0013f6b0  0a 48 6f 6d 65 44 6f 6d  61 69 6e 00 2f 4c 69 62  |.HomeDomain./Lib|
0013f6c0  72 61 72 79 2f 4b 65 79  62 6f 61 72 64 2f 50 68  |rary/Keyboard/Ph|
0013f6d0  72 61 73 65 4c 65 61 72  6e 69 6e 67 5f 6a 61 5f  |raseLearning_ja_|
0013f6e0  4a 50 2e 64 62 2e 62 75  6e 64 6c 65 ff ff ff ff  |JP.db.bundle....|
0013f6f0  ff ff 41 ed 00 00 00 00  00 00 0a 9f 00 00 01 f5  |..A.............|
0013f700  00 00 01 f5 52 e5 f6 bd  52 e5 f6 bd 52 de 02 60  |....R...R...R..`|
...(中略)...
  • 目次があるのに、ページだけ削除するから、バックアップが壊れていると警告されてしまうのだ。
  • 目次情報のCloudUserDictionary.sqliteを削除しておけば、警告されずに復元できるかもしれない!

しかし、Manifest.mbdbはバイナリファイルである。必要な部分だけ、1バイトも間違わずに削除するなんてことが、果たしてできるだろうか?

Manifest.mbdbを解析する

  • 解析するなんて書いたが、正直、自分にはさっぱり...。
  • テキスト部分はある程度予想できたが、テキストではないバイナリデータの部分は、訳が分からない。
  • しかし、世の中にはすごい達人がいた。自分が行うまでもなく、.mbdbはすでに解析されていたのだ。
  • つまり、バイナリデータを項目別に解析してみると、以下のように読み取れる。
0013f520  00 00 0a 48 6f 6d 65 44  6f 6d 61 69 6e 00 44 4c  |...HomeDomain.DL|
0013f530  69 62 72 61 72 79 2f 4b  65 79 62 6f 61 72 64 2f  |ibrary/Keyboard/|
0013f540  50 68 72 61 73 65 4c 65  61 72 6e 69 6e 67 5f 7a  |PhraseLearning_z|
0013f550  68 5f 48 61 6e 74 5f 70  69 6e 79 69 6e 2e 64 62  |h_Hant_pinyin.db|
0013f560  2e 62 75 6e 64 6c 65 2f  64 61 74 61 62 61 73 65  |.bundle/database|
0013f570  2e 64 62 ff ff 00 14 01  18 48 19 51 c6 53 36 01  |.db......H.Q.S6.|
0013f580  26 1d 9e 93 79 fa 7c de  e4 37 5e ff ff 81 a4 00  |&...y.|..7^.....|
0013f590  00 00 00 00 06 87 ab 00  00 01 f5 00 00 01 f5 52  |...............R|
0013f5a0  e5 f6 be 52 e5 f1 f5 52  e5 c1 bc 00 00 00 00 00  |...R...R........|
0013f5b0  00 50 00 04 00 00 0a 48  6f 6d 65 44 6f 6d 61 69  |.P.....HomeDomai|
0013f5c0  6e 00 31 4c 69 62 72 61  72 79 2f 4b 65 79 62 6f  |n.1Library/Keybo|
0013f5d0  61 72 64 2f 50 68 72 61  73 65 4c 65 61 72 6e 69  |ard/PhraseLearni|
0013f5e0  6e 67 5f 7a 68 5f 48 61  6e 73 2e 64 62 2e 62 75  |ng_zh_Hans.db.bu|
0013f5f0  6e 64 6c 65 ff ff ff ff  ff ff 41 ed 00 00 00 00  |ndle......A.....|
  • stringは、文字数とASCIIコードで構成される、可変長のデータ。
データ種 項目名 文字数 ASCIIコード テキストの内容
string ドメイン 00 0a 48 6f 6d 65 44 6f 6d 61 69 6e HomeDomain
string ファイルパス 00 44 4c 69 62 72 61 72 79 2f 4b 65 79 62 6f 61 72 64
2f 50 68 72 61 73 65 4c 65 61 72 6e 69 6e 67 5f
7a 68 5f 48 61 6e 74 5f 70 69 6e 79 69 6e 2e 64
62 2e 62 75 6e 64 6c 65 2f 64 61 74 61 62 61 73
65 2e 64 62
Library/Keyboard/PhraseLearning_zh_Hant_pinyin.db.bundle/database.db
string シンボルのリンク先 ff ff ff ff=nullの意味?
string データのハッシュ値 00 14 01 18 48 19 51 c6 53 36 01 26 1d 9e 93 79 fa 7c
de e4 37 5e
ファイル内容のSHA.1ハッシュ値
string 不明 ff ff ff ff=nullの意味?
  • unitはバイト数が決められた、固定長のデータ。
データ種 項目名 内容 意味
unit16 モード 81 a4 0100644(8xxx=ファイル、4xxx=ディレクトリ、axxx=シンボリックリンク
unit32 不明 00 00 00 00
unit32 不明 00 06 87 ab
unit32 ユーザーID 00 00 01 f5 501
unit32 グループID 00 00 01 f5 501
unit32 日時 52 e5 f6 be 2014/01/27 15:03:42(mtime)
unit32 日時 52 e5 f1 f5 2014/01/27 14:43:17(atime)
unit32 日時 52 e5 c1 bc 2014/01/27 11:17:32(ctime)
unit64 ファイルサイズ 00 00 00 00 00 00 50 00 20480
unit8 フラッグ 04
unit8 プロパティの数 00
$ ruby -e 'puts 0x81a4.to_s(8)'
100644

$ ruby -r time -e 'puts Time.at(0x52e5f6be).strftime("%Y/%m/%d %H:%M:%S")'
2014/01/27 15:03:42

$ ruby -r time -e 'puts Time.parse("2014/01/27 15:03:42").to_i.to_s(16)'
52e5f6be
  • もし、プロパティが存在すれば、以下の「キー」と「値」のペアが「プロパティの数」だけ続くはず。
データ種 項目名 バイト数 ASCIIコード
string キー キーのバイト数 キーの名称
string 値のバイト数 値の内容

mbdbを解釈するコード

  • ところで、今回の目的はバックアップファイルから不要な設定ファイルを削除することである。
  • そこで、上記ページで紹介されているコードを元に、二つの変換コードを作ってみた。
    • mbdbを1レコード1行のテキストファイルに変換するコード。
    • 上記テキストファイルを元にmbdbに再変換するコード。
mbdb2text.rb(mbdbをテキストに変換)
# encoding: utf-8
require 'fileutils'

class ManifestParser
  def initialize(mbdb_filename, verbose = false)
    @verbose = verbose
    process_mbdb_file(mbdb_filename)
  end

  def to_file(filename)
    File.open(filename, 'w') do |f|
      @mbdb.each do |v|
        f.puts fileinfo_str(v)
      end
    end
  end

  private
  # Retrieve an integer (big-endian) and new offset from the current offset
  def getint(data, offset, intsize)
    value = 0
    while intsize > 0
      value = (value<<8) + data[offset].ord
      offset += 1
      intsize -= 1
    end
    return value, offset
  end

  # Retrieve a string and new offset from the current offset into the data
  def getstring(data, offset)
    return 'ffff', offset + 2 if data[offset] == 0xFF.chr and data[offset + 1] == 0xFF.chr # Blank string
    length, offset = getint(data, offset, 2) # 2-byte length
    value = data[offset...(offset + length)]
    value = '0000' if value == ""
    return value, (offset + length)
  end

  def process_mbdb_file(filename)
    @mbdb = Array.new
    data = File.open(filename, 'rb') { |f| f.read }
    puts "MBDB file read. Size: #{data.size}"
    raise 'This does not look like an MBDB file' if data[0...4] != 'mbdb'
    offset = 4
    offset += 2 # value x05 x00, not sure what this is
    while offset < data.size
      fileinfo = Hash.new
      fileinfo[:start_offset] = offset
      fileinfo[:domain], offset = getstring(data, offset)
      fileinfo[:filename], offset = getstring(data, offset)
      fileinfo[:linktarget], offset = getstring(data, offset)
      fileinfo[:datahash], offset = getstring(data, offset)
      #fileinfo[:datahash] = fileinfo[:datahash].each_codepoint.to_a.map{|i|i.to_s(16)}.join("") if fileinfo[:datahash] != "ffff"
      fileinfo[:unknown1], offset = getstring(data, offset)
      fileinfo[:mode], offset = getint(data, offset, 2)
      fileinfo[:unknown2], offset = getint(data, offset, 4)
      fileinfo[:unknown3], offset = getint(data, offset, 4)
      fileinfo[:userid], offset = getint(data, offset, 4)
      fileinfo[:groupid], offset = getint(data, offset, 4)
      fileinfo[:mtime], offset = getint(data, offset, 4)
      fileinfo[:atime], offset = getint(data, offset, 4)
      fileinfo[:ctime], offset = getint(data, offset, 4)
      fileinfo[:filelen], offset = getint(data, offset, 8)
      fileinfo[:flag], offset = getint(data, offset, 1)
      fileinfo[:propertynum], offset = getint(data, offset, 1)
      fileinfo[:properties] = Hash.new
      (0...(fileinfo[:propertynum])).each do |ii|
        propname, offset = getstring(data, offset)
        propval, offset = getstring(data, offset)
        #propval = propval.each_codepoint.to_a.map{|i|i.to_s(16)}.join("")
        fileinfo[:properties][propname] = propval
      end
      @mbdb << fileinfo
    end
    @mbdb
  end

  def fileinfo_str(f)
    return "(#{f[:fileID]})#{f[:domain]}::#{f[:filename]}" unless @verbose
    data = [ f[:domain], f[:filename], f[:linktarget], f[:datahash], f[:unknown1], f[:mode], f[:unknown2], f[:unknown3], f[:userid], f[:groupid], f[:mtime], f[:atime], f[:ctime], f[:filelen], f[:flag], f[:propertynum], f[:properties]]
    #info = "%s %s %s %s %s %04x %08x %08x %08x %08x %08x %08x %08x %016x %02x %02x" % data
    info = data.inspect
    info
  end
end

if __FILE__ == $0
  mp = ManifestParser.new('Manifest.mbdb', true)
  mp.to_file('Manifest.mbdb.txt')
end
text2mbdb.rb(テキストからmbdbに変換)
# encoding: ASCII-8BIT
require 'fileutils'

class ManifestParser
  def initialize(mbdb_filename, verbose = false)
    @verbose = verbose
    process_mbdb_file(mbdb_filename)
  end

  def to_file(filename)
    File.open(filename, 'wb') do |f|
      f.write(@mbdb)
    end
  end

  private
  # Retrieve an integer (big-endian) and new offset from the current offset
  def getint(data, intsize)
    data16 = "%0#{intsize*2}x" % data
    [data16].pack('H*')
  end

  # Retrieve a string and new offset from the current offset into the data
  def getstring(str)
    return "\xff\xff" if str == 'ffff'
    return "\x00\x00" if str == '0000'
    return [str.length].pack('n') + str
  end

  def process_mbdb_file(filename)
    @mbdb = "mbdb\x05\x00"
    data = File.open(filename, 'rb') { |f| f.read }
    puts "MBDB file read. Size: #{data.size}"
    data.each_line do |line|
      #a = line.chomp.split
      a = eval(line)
      @mbdb << getstring(a[0])
      @mbdb << getstring(a[1])
      @mbdb << getstring(a[2])
      @mbdb << getstring(a[3])
      @mbdb << getstring(a[4])
      @mbdb << getint(a[5], 2)
      @mbdb << getint(a[6], 4)
      @mbdb << getint(a[7], 4)
      @mbdb << getint(a[8], 4)
      @mbdb << getint(a[9], 4)
      @mbdb << getint(a[10], 4)
      @mbdb << getint(a[11], 4)
      @mbdb << getint(a[12], 4)
      @mbdb << getint(a[13], 8)
      @mbdb << getint(a[14], 1)
      @mbdb << getint(a[15], 1)
      a[16].each do |k, v|
        @mbdb << getstring(k)
        @mbdb << getstring(v)
      end
    end
    @mbdb
  end

  def fileinfo_str(f)
    return "(#{f[:fileID]})#{f[:domain]}::#{f[:filename]}" unless @verbose
    data = [ f[:domain], f[:filename], f[:linktarget], f[:datahash], f[:unknown1], f[:mode], f[:unknown2], f[:unknown3], f[:userid], f[:groupid], f[:mtime], f[:atime], f[:ctime], f[:filelen], f[:flag],f[:propertynum]]
    info = "%s %s %s %s %s %04x %08x %08x %08x %08x %08x %08x %08x %016x %02x %02x" % data
    f[:properties].each do |k, v|
      info += " #{k}=#{v.inspect}"
    end
    info
  end
end

if __FILE__ == $0
  mp = ManifestParser.new('Manifest.mbdb.txt', true)
  mp.to_file('Manifest.txt.mbdb')
end

実行

かなりやっつけなコードだけど、これで用が足りる。

  • デスクトップにmbdb2text.rb、text2mbdb.rb、Manifest.mbdbを置いて...
  • mbdb2text.rbを実行すると、テキストに変換されたManifest.mbdb.txtが生成される。
$ ruby mbdb2text.rb
  • Manifest.mbdb.txtを開いて、不要なファイル情報の行を削除する。
  • その後、text2mbdb.rbを実行すると、削除したファイル情報を含まないManifest.mbdb.txtが生成されるのだ。
$ ruby text2mbdb.rb
  • Manifest.mbdb.txtを~/Library/Application Support/MobileSync/Backup/XXXXXXXXXXXXXX/Manifest.mbdbに移動して、準備完了。
  • iTunesで「バックアップを復元...」してみる。
  • 今度はエラー警告されずに、復元が完了した!
  • 最初は、いくつか存在するCloudUserDictionary.sqliteファイルのみを削除しての試みだった。
  • ユーザ辞書は何も登録がない状態になるのだけど、iCloudとの同期はうまく機能していないようだ。
  • えーい、こうなったらLibrary/Keyboard/を含むすべてのパスを削除してしまえ!とヤケになった。
    • 大丈夫、元のManifest.mbdbはちゃんとバックアップしてあるのだ。
    • Manifest.mbdb.txtから、以下の行をゴソっと一気に削除してみた。
["HomeDomain", "Library/Keyboard", "ffff", "ffff", "ffff", 16832, 0, 49, 501, 501, 1390785465, 1390785465, 1390281141, 0, 4, 0, {}]
["HomeDomain", "Library/Keyboard/dynamic-text.dat", "ffff", "\n\x1F[L\xB5$\r\x9Az\x02?\x9D\xC9ja\xEB\xC1\xA2\x05\xC2", "ffff", 33152, 0, 234135, 501, 0, 1390336046, 1390336046, 1390336046, 48, 4, 0, {}]
["HomeDomain", "Library/Keyboard/PhraseLearning_zh_Hant_zhuyin.db.bundle", "ffff", "ffff", "ffff", 16877, 0, 64154, 501, 501, 1390785301, 1390785301, 1390268470, 0, 4, 0, {}]
["HomeDomain", "Library/Keyboard/PhraseLearning_zh_Hant_zhuyin.db.bundle/database.db", "ffff", "e&\xF0#3\xEA\x7F\xBDs\xD8\tn;\xBA\xE9`q\x96\x03\a", "ffff", 33188, 0, 64183, 501, 501, 1390288790, 1390284436, 1390268470, 20480, 4, 0, {}]
["HomeDomain", "Library/Keyboard/PhraseLearning_zh_Hant_pinyin.db.bundle", "ffff", "ffff", "ffff", 16877, 0, 64155, 501, 501, 1390785301, 1390785301, 1390268470, 0, 4, 0, {}]
["HomeDomain", "Library/Keyboard/PhraseLearning_zh_Hant_pinyin.db.bundle/database.db", "ffff", "\x01\x18H\x19Q\xC6S6\x01&\x1D\x9E\x93y\xFA|\xDE\xE47^", "ffff", 33188, 0, 64188, 501, 501, 1390288789, 1390284436, 1390268470, 20480, 4, 0, {}]
["HomeDomain", "Library/Keyboard/PhraseLearning_zh_Hans.db.bundle", "ffff", "ffff", "ffff", 16877, 0, 64156, 501, 501, 1390785301, 1390785301, 1390268467, 0, 4, 0, {}]
["HomeDomain", "Library/Keyboard/PhraseLearning_zh_Hans.db.bundle/database.db", "ffff", "\xD9\x19\xDA\xEA`\xFA\xD2)\xB8\x7Fj\n\xF9\xBF\xBDu\xA4{Hq", "ffff", 33188, 0, 64196, 501, 501, 1390288788, 1390284436, 1390268467, 20480, 4, 0, {}]
["HomeDomain", "Library/Keyboard/PhraseLearning_ja_JP.db.bundle", "ffff", "ffff", "ffff", 16877, 0, 2719, 501, 501, 1390785465, 1390785465, 1390281312, 0, 4, 0, {}]
["HomeDomain", "Library/Keyboard/Lexierra_ja_JP-dynamic-text.dat", "ffff", "\xDA9\xA3\xEE^kK\r2U\xBF\xEF\x95`\x18\x90\xAF\xD8\a\t", "ffff", 33152, 0, 64197, 501, 501, 1379583290, 1390284436, 1379583290, 0, 4, 0, {}]
["HomeDomain", "Library/Keyboard/CoreDataUbiquitySupport", "ffff", "ffff", "ffff", 16877, 0, 399096, 501, 501, 1390785378, 1390785378, 1390785378, 0, 4, 0, {}]
["HomeDomain", "Library/Keyboard/CoreDataUbiquitySupport/mobile~E88C661C-2206-5E67-AA19-66EC6D3EC50E", "ffff", "ffff", "ffff", 16877, 0, 399097, 501, 501, 1390785378, 1390785378, 1390785378, 0, 4, 0, {}]
["HomeDomain", "Library/Keyboard/CoreDataUbiquitySupport/mobile~E88C661C-2206-5E67-AA19-66EC6D3EC50E/UserDictionary", "ffff", "ffff", "ffff", 16877, 0, 399098, 501, 501, 1390785401, 1390785401, 1390785378, 0, 4, 0, {}]
["HomeDomain", "Library/Keyboard/CoreDataUbiquitySupport/mobile~E88C661C-2206-5E67-AA19-66EC6D3EC50E/UserDictionary/local", "ffff", "ffff", "ffff", 16877, 0, 399099, 501, 501, 1390785378, 1390785378, 1390785378, 0, 4, 0, {}]
["HomeDomain", "Library/Keyboard/CoreDataUbiquitySupport/mobile~E88C661C-2206-5E67-AA19-66EC6D3EC50E/UserDictionary/local/store", "ffff", "ffff", "ffff", 16877, 0, 399100, 501, 501, 1390785385, 1390785385, 1390785378, 0, 4, 0, {}]
["HomeDomain", "Library/Keyboard/CoreDataUbiquitySupport/mobile~E88C661C-2206-5E67-AA19-66EC6D3EC50E/UserDictionary/079F2A5D-20CE-4391-9E46-BD3C829C98CC", "ffff", "ffff", "ffff", 16877, 0, 399294, 501, 501, 1390785401, 1390785401, 1390785401, 0, 4, 0, {}]
["HomeDomain", "Library/Keyboard/CoreDataUbiquitySupport/mobile~E88C661C-2206-5E67-AA19-66EC6D3EC50E/UserDictionary/079F2A5D-20CE-4391-9E46-BD3C829C98CC/store", "ffff", "ffff", "ffff", 16877, 0, 399295, 501, 501, 1390785685, 1390785685, 1390785401, 0, 4, 0, {}]
["HomeDomain", "Library/Keyboard/BigramLearning_ja_JP.db.bundle", "ffff", "ffff", "ffff", 16877, 0, 2729, 501, 501, 1390785465, 1390785465, 1390281312, 0, 4, 0, {}]
["HomeDomain", "Library/Keyboard/.DictionaryMigrationLock.lock", "ffff", "\xDA9\xA3\xEE^kK\r2U\xBF\xEF\x95`\x18\x90\xAF\xD8\a\t", "ffff", 33188, 0, 399889, 501, 501, 1390785465, 1390785465, 1390785465, 0, 4, 0, {}]
["HomeDomain", "Library/Keyboard/CoreDataUbiquitySupport/mobile~E88C661C-2206-5E67-AA19-66EC6D3EC50E/UserDictionary/079F2A5D-20CE-4391-9E46-BD3C829C98CC/store/CloudUserDictionary.sqlite", "ffff", "\xBB>\x91\xBEF\x92B5\xD1\x0E\xDE\xF3\x90\xFF`B\x1A\xF3\x12\x89", "ffff", 33188, 0, 399297, 501, 501, 1390785685, 1390785685, 1390785401, 40960, 4, 0, {}]
["HomeDomain", "Library/Keyboard/BigramLearning_ja_JP.db.bundle/database.db", "ffff", "*\xE9k\xBB\x88\xD1\xF8\bo\xA4\x96b\xC0\xEC\x17Df\xB0\xCE\x8A", "ffff", 33188, 0, 397243, 501, 501, 1390785465, 1390785243, 1390779824, 20480, 4, 0, {}]
["HomeDomain", "Library/Keyboard/PhraseLearning_ja_JP.db.bundle/database.db", "ffff", ":\xC0\xD1\xDF\xC3/\x0FWl\xB2\x98\x04\xB9%\xB7\xF2\x1E\x96:\xBE", "ffff", 33188, 0, 397245, 501, 501, 1390785713, 1390785243, 1390782010, 20480, 4, 0, {}]
["HomeDomain", "Library/Keyboard/CoreDataUbiquitySupport/mobile~E88C661C-2206-5E67-AA19-66EC6D3EC50E/UserDictionary/local/store/CloudUserDictionary.sqlite", "ffff", "\xB8\x11F\x94k\xCC\xF6\xA4\xC7\x92\x86\xDA\xEB\xECB\x04<\xDA\xDC\x91", "ffff", 33188, 0, 399101, 501, 501, 1390785384, 1390785384, 1390785378, 49152, 4, 0, {}]

すると、iPhoneMacBookのユーザ辞書が同期し始めた!

ユーザ辞書が同期するまでに試したこと

最終的にはiPhoneのLibrary/Keyboard/をリセットできたから、ユーザ辞書が正常に同期し始めたと思っているのだが、ここに辿り着くまでに、いくつもの手順を踏んでいる。それらの手順もユーザ辞書の同期を再開させるために重要かもしれないので、忘れないようにメモ。

すべての端末で、iCloudの「書類とデータ」の同期をオフ
  • MacBookiPhoneiCloud経由で同期する場合、3つの領域が同期に関与していると理解した。
    • iCloudサーバーとして、同期情報を保存しておく領域。
    • MacBookとして、同期情報を保存しておく領域。
    • iPhoneとして、同期情報を保存しておく領域。
  • iCloudを経由する同期のリセットの難しさは、どれか1つの領域をリセットしても、すぐまた別の領域の情報と同期してしまって、完全なリセットを実行しにくいこと。
  • すべての端末で、リセットしたい同期をオフにすることで、確実にリセットできる環境になる。
すべての同期領域を同じタイミングでリセット
  • 同期をオフにしたら、すべての同期領域を速やかにリセットするのだ。
    • iCloudサーバーの場合:https://www.icloud.com にアクセスして、アカウント設定 >> 詳細設定 >> 書類とデータのリセット。(Safariでアクセスした)
    • MacBookの場合:~/Library/Mobile Documents/com~apple~TextInput/ の削除。(Finderで削除した)
    • iPhoneの場合:Library/Keyboard/ の削除。(この日記の手順で削除した)
その他試行錯誤の中でやったこと
  • iCloudからのサインアウト
    • MacBookの場合:システム環境設定 >> iCloud からサインアウト
    • iPhoneの場合:設定 >> iCloud >> アカウントを削除
      • iCloudのアカウントが削除される訳ではなく、iPhone中の同期情報のみ削除される。
      • 再び同じアカウントでiCloudにサインインすれば、同期情報は復元されるのだ。
  • iCloudにサインインするアカウント名称を@以降まで統一。
    • 自分は現在、3つのアップルIDでサインインできてしまう。
      • ユーザー名@mac.com
      • ユーザー名@me.com
      • ユーザー名@icloud.com
  • どれを使っても同一のiCloudアカウントにサインインしていると思うのだが、
  • 念のため、すべての端末で「ユーザー名@icloud.com」に統一して、サインインしておいた。
  • iCloudにサインインする時、すべての情報が同期するまでには、かなりの時間がかかった。
    • 特にユーザ辞書のデータは、同期のタイミングが遅い気がした。
    • アクティビティモニタの受信パケット数を見ながら、気長に待った。(1時間くらい)