環境
下記環境でiOS開発している。
- XCode7
問題点
CoreDataでsqliteを利用したアプリ開発をしていると、シミュレーターで登録したデータの確認をするなどの目的で、sqliteのファイルを直接見たいことがある。
但し、sqliteのファイルパスが直感的ではないため探すのが面倒臭い。
ファイルパス
sqliteは、以下のディレクトリ構成で格納されるようだ。
~/Library/Developer/CoreSimulator/Devices/[デバイス固有文字列]/data/Containers/Data/Application/[アプリケーション固有文字列]/Documents/[アプリケーション名].sqlite
上記パスの下記文字列がランダムな文字列になっている。
- デバイス固有文字列
- アプリケーション固有文字列
このうちデバイス固有文字列は、以下のファイルの中の「dict -> dict -> string」に定義されている。
~/Library/Developer/CoreSimulator/Devices/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を解析して、これらの一覧を表示できるスクリプトを作ってみた。
#! /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とか)。
こういうアプリだとメニューバーに常駐させてグラフィカルに操作できたりするので便利そうだ。
※Scala版も作成してみました。