[XCode][iOS] sqliteのファイルパスを簡単に探すためのスクリプト

# 環境

下記環境でiOS開発している。

* XCode7

# 問題点

CoreDataでsqliteを利用したアプリ開発をしていると、シミュレーターで登録したデータの確認をするなどの目的で、sqliteのファイルを直接見たいことがある。

但し、sqliteのファイルパスが直感的ではないため探すのが面倒臭い。

# ファイルパス

sqliteは、以下のディレクトリ構成で格納されるようだ。

```:sqliteのファイルパス
~/Library/Developer/CoreSimulator/Devices/[デバイス固有文字列]/data/Containers/Data/Application/[アプリケーション固有文字列]/Documents/[アプリケーション名].sqlite
```

上記パスの下記文字列がランダムな文字列になっている。

  * デバイス固有文字列
  * アプリケーション固有文字列

このうちデバイス固有文字列は、以下のファイルの中の「dict -> dict -> string」に定義されている。

```:DeviceID一覧が記載されているファイル
~/Library/Developer/CoreSimulator/Devices/device_set.plist
```

このファイルの中身は、以下のようになっている。

```xml:device_set.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>DefaultDevices</key>
	<dict>
		<key>com.apple.CoreSimulator.SimRuntime.iOS-8-0</key>
		<dict>
			<key>com.apple.CoreSimulator.SimDeviceType.Resizable-iPad</key>
			<string>英数字のランダム文字列がハイフン区切りで格納されている</string>
			<key>com.apple.CoreSimulator.SimDeviceType.Resizable-iPhone</key>
			<string>英数字のランダム文字列がハイフン区切りで格納されている</string>
             :
           <省略>
```

# 簡単にパスを取得する方法

ターミナルで以下のコマンドを実行すれば、sqliteのファイル一覧を日付更新順(降順)に出力できる。

```
% ls -t ~/Library/Developer/CoreSimulator/Devices/*/data/Containers/Data/Application/*/Documents/*.sqlite
```

sqliteのファイル名にアプリケーションの名称が入るため、開発しているアプリの数が少なければ、このコマンドを実行するだけで充分かもしれない。

ただし、同じアプリでも、複数のiOSのバージョンや複数のデバイスで動作確認をすると思うので、デバイス固有文字列と、

  * iOSバージョン(iOS-9-2, iOS-9-3,...)
  * モデル(iPhone-6s、iPad-Air,...)

の対応付けをしておくと便利だと思う。

そこで、device_set.plistを解析して、これらの一覧を表示できる[スクリプト](https://gist.github.com/eidera/dea663c034dd320bb601307cc7ff9168)を作ってみた。

```ruby:getXcodeAppliPath.rb
#! /usr/bin/env ruby

require 'rexml/document'

# File path : device_set.plist
DEVICE_SET_PLIST="#{Dir.home}/Library/Developer/CoreSimulator/Devices/device_set.plist"
SQLITE_LIST = "#{Dir.home}/Library/Developer/CoreSimulator/Devices/*/data/Containers/Data/Application/*/Documents/*.sqlite"

# Device information
class Device
  def initialize(os, model)
    @os = os
    @model = model
  end

  attr_reader :os, :model

  def to_s()
    sprintf("%s %s", os , model)
  end
end

# This class hold all device information of defined by device_set.plist
class DeviceHolder
  def initialize()
    @device = {}
  end

  def push(id, os, model)
    @device[id] = Device.new(os, model)
  end

  def get(id)
    @device[id]
  end

  def ids
    @device.key
  end

  def to_s(id)
    get(id).to_s
  end
end

class DeviceSetPlistReader
  def initialize(filename)
    @filename = filename
    @holder = DeviceHolder.new
  end

  def execute
    doc = REXML::Document.new(open(@filename))

    default_device = doc.root.elements['dict/dict']

    os_name = nil
    default_device.elements.each do |el|
      if 'key' == el.name
        os_name = trimming(el.text)
      elsif 'dict' == el.name
        read_device_id(el, os_name)
      end
    end

    @holder
  end

  private

  def trimming(text)
    text.split('.').last
  end

  def read_device_id(target, os_name)
    # Ex.
    #   <key>com.apple.CoreSimulator.SimDeviceType.Resizable-iPad</key>
    #   <string>DEVICE_ID1</string>
    #   <key>com.apple.CoreSimulator.SimDeviceType.Resizable-iPhone</key>
    #   <string>DEVICE_ID2</string>
    model = nil
    target.elements.each do |el|
      if 'key' == el.name
        model = trimming(el.text)
      elsif 'string' == el.name
        @holder.push(el.text, os_name, model)
      end
    end
  end
end

#### Start process

reader = DeviceSetPlistReader.new(DEVICE_SET_PLIST)
holder = reader.execute

Dir.glob(SQLITE_LIST).sort{|x,y| -1*(File::stat(x) <=> File::stat(y))}.each do |file|
  dirs = file.split('/')
  device_id = dirs[-8] # Device ID is in the 8th from behind.
  application_name = dirs[-1].sub(/.sqlite$/, '')
  printf("%s\n  %s\n    %s\n", application_name, holder.to_s(device_id), file)
end
```

実行すると以下のようになる。

```
% ruby getXcodeAppliPath.rb
DummyAppli
  iOS-9-3 iPhone-6s
    /Users/hoge/Library/Developer/CoreSimulator/Devices/XXXX-XXXX-XXXX/data/Containers/Data/Application/XXXX-XXXX-XXXX/Documents/DummyAppli.sqlite
DummyAppli
  iOS-9-2 iPhone-6s
    /Users/hoge/Library/Developer/CoreSimulator/Devices/XXXX-XXXX-XXXX/data/Containers/Data/Application/XXXX-XXXX-XXXX/Documents/DummyAppli.sqlite
```

# さいごに

ググると、アプリケーションのパスを表示してくれるソフトは、有料だといくつかあるらしい(ex. [SimPholders](https://simpholders.com/)とか)。
こういうアプリだとメニューバーに常駐させてグラフィカルに操作できたりするので便利そうだ。

※[Scala版](http://qiita.com/eidera/items/3b20735547a2bd3022c8)も作成してみました。