Apple Watch Appのライフサイクルまとめ

この投稿はWatchKit Advent Calendar 2014の7日目の記事です。

前提知識

Watch Appのアーキテクチャ

Apple Watch側にStoryboardやリソースファイルを置いて、iPhoneのWatchKit Extension側で処理を行います。
UIの更新などApple WatchとiPhone間の通信はWatchKitが行います。

app_communication_2x
(via WatchKit Programming Guide: Watch App Architecture)

Watch Appのライフサイクル

ユーザがApple WatchであなたのWatch Appを起動すると、自動的にiPhone側のWatchKit Extensionを起動します。
ユーザの操作が終わると、iOSはWatchKit Extensionの実行を中断します。
つまり、WatchKit Extensionは、ユーザがApple WatchであなたのWatch Appを操作中の時のみ実行できます。

launch_cycle_2x
(via WatchKit Programming Guide: Watch App Architecture)

WKInterfaceControllerでのライフサイクルハンドリング

下記図のとおりです。

watch_app_lifecycle_simple_2x
(via WatchKit Programming Guide: Watch App Architecture)

initWithContext:

WKInterfaceControllerが作成されて、最初に呼ばれるメソッドです。
データのロードや、ラベル、画像、テーブルなどのオブジェクトを更新するために使用します。
contextでHandoffや他の画面から値を受け取ることができます。

willActivate

ユーザにUIが表示されたタイミングで呼ばれるメソッドです。
UIの更新や、UIがアクティブなときだけ発生するようなタスクの実行に使用します。
例えば、タイマーのセットや、アニメーションの開始、ビデオのストリーミング開始(ぇ、そんなのできたっけ?)などです。

didDeactivate

ユーザがApple Watchであなたのアプリを操作することをやめると呼ばれるメソッドです。
クリーンアップの処理を行います。
例えば、タイマーの停止や、アニメーションの停止、ビデオのストリーミング停止などです。
次にwillActivateが呼ばれるまでの間は、UIに値をセットしても無視されます。

複数画面ある場合は?

実験してみました。
First、Second、Thirdという名前のWKInterfaceControllerを作成し、
First->Second->Third->Second->Firstの順に画面遷移し、ライフサイクルハンドリングするメソッドでログを吐いてみました。

以下ログ。

FirstのinitWithContext
FirstのwillActive
SecondをPush
SecondのinitWithContext
SecondのwillActivate
FirstのdidDeactivate
ThirdをPush
ThirdのinitWithContext
ThirdのwillActivate
SecondのdidDeactivate
ThirdをPop
SecondのwillActivate
ThirdのdidDeactivate
SecondをPop
FirstのwillActivate
SecondのdidDeactivate

次の画面がwillActivateになったら、自身がdidDeactivateになるようです。

宣伝

Swiftでカスタムキーボードアプリ作りました。買ってね!
特殊文字キーボード


WKInterfaceDeviceクラスの全プロパティ・メソッド解説

この投稿はWatchKit Advent Calendar 2014の6日目の記事です。

WKInterfaceDevice

Apple Watchのデバイス情報を取得するためのクラスです。
以下すべてのメソッドとプロパティの解説です。

Getting the Shared Device Object

  • currentDevice
    デバイスオブジェクトを取得します。

Getting the Screen Information

  • screenBounds
    画面のサイズのCGRectが取得できます。

  • screenScale
    画面のスケールが取得できます。Apple Watchは2.0です。

Getting the Device Settings

  • currentLocale
    ロケールの設定をNSLocaleで取得できます。
    NSLocaleについてはこちら参照。
    NSLocale Class Reference

  • preferredContentSizeCategory
    ユーザはOSの設定でアプリに表示するアプリのサイズ(大きめ、小さめ)を要求できます。
    システムがreturnしたフォントオブジェクトはこの設定に応じて自動的にリサイズされます。
    コードからフォントオブジェクトを要求する場合、適切なサイズのフォントオブジェクトを要求するために、このプロパティの値を使用します。
    このプロパティの値については以下を参照してください。
    Content Size Category Constants – UIApplication Class Reference

Managing Image Caches on the Device

画像をキャッシュできるようです。
@tidさんの詳しい解説があるので省略します。
Swift – WatchKitでWatchに画像をキャッシュする – Qiita

まとめ

シンプル。必要最低限という感じですね…

宣伝

Swiftでカスタムキーボードアプリ作りました。買ってね!
特殊文字キーボード


WKInterfaceObjectクラスの全プロパティ・メソッド解説

この投稿はWatchKit Advent Calendar 2014の5日目の記事です。

WKInterfaceObjectとは

WatchKitに含まれるすべてのUIパーツのベースとなるクラスです。
つまり、WKInterfaceObjectを理解すれば、WatchKitのUIパーツすべて理解できます。たぶん。

WKInterfaceObjectはなんと、継承したり、コードでインスタンスを作ったりはできません。
すべてのUIパーツはStoryboardで作成する必要があります。
コードからUIパーツを弄りたい場合は、以下のようにしてStoryboardから参照します。

@property (weak, nonatomic) IBOutlet WKInterfaceButton* myButton;

詳しくはリファレンスを参照してください。

Hiding and Showing the Object

  • setHidden:
    iOSのUIViewと同じです。インスタンスを作ることはできないけど、隠すことはできるみたいです。

  • setAlpha:
    アルファを0.0から1.0の間で指定できます。0.0だと完全に透明で見えなくなります。

Getting the Property Name

  • interfaceProperty
    Storyboard上のオブジェクトとコードで扱うオブジェクトを対応させるためにWatchKitが内部的に使用する値のようです。
    開発者がこの値を直接使用する必要はないそうです。

Changing an Object’s Size

  • setWidth:
    Widthを指定します。
    0.0をセットすると、無視されてStoryboardの値が使用されます。

  • setHeight:
    Heightを指定します。
    0.0をセットすると、無視されてStoryboardの値が使用されます。

Configuring the Accessibility Attributes

  • setAccessibilityLabel:
    オブジェクトの簡潔な説明をセットします。

  • setAccessibilityHint:
    ユーザがオブジェクトを操作した時に何が起こるかの説明をセットします。

  • setAccessibilityLabel:
    オブジェクトの値をセットします。(スライダーやスイッチで使用する?)

そもそもAccessibilityとは

iOSの設定でアクセシビリティという項目がありますが、
VoiceOverやAssistiveTouchなど障害者の方向けの機能(?)などが
含まれています。

下記ページに詳しく書かれています。

iOSアプリ開発とアクセシビリティ対応について | Technology-Gym

Accessibilityに値をセットするとどんな挙動になるの?

試しにSliderを作って、適当な値をセットし、アクセシビリティインスペクタで確認してみましたが何も起きませんでした。
Apple Watchにはどのようなアクセシビリティの機能があるんでしょうか…

まとめ

以上、WKInterfaceObjectの全メソッド解説でした。
全体的にsetterしかないんですね。
Accessibilityがどんな挙動になるんでしょうか。楽しみです。

宣伝

Swiftでカスタムキーボードアプリ作りました。買ってね!
特殊文字キーボード


WatchKitでpush画面遷移

この投稿はWatchKit Advent Calendar 2014の4日目の記事です。

Apple WatchでのPush画面遷移

Apple Watchでは以下の3種類の画面遷移があるようです。

  • push
  • modal
  • page (正式名称はわかりませんがページをめくるような遷移)

今回はpushのお話です。

リファレンス

今回もリファレンスを確認してWKInterfaceControllerに怪しいAPIがありました。

WKInterfaceController

WKInterfaceController Class Reference

以下の3つのAPIが関係ありそうです。
* pushControllerWithName:context:
* popController
* popToRootController

実装

Storyboard

以下のようにします。
storyboard

緑の四角形はボタンです。

コード

FirstInterfaceController.swift

// pushSecondボタン押下時の処理
@IBAction func onPushSecondButtonPushed() {
    pushControllerWithName("secondInterfaceControllerId", context: "gotoSecond!!")
}

引数nameには遷移先のInterfaceControllerのIdentifierを指定します。
IdentifierはStoryboardで設定します。
contextには遷移先のInterfaceControllerに受渡したい値を指定します。
contextはAnyObject!なのでなんでも受け渡せそうです。
contextはnilを指定することもできますが、おすすめはしません。(…とリファレンスに書いてありますが理由はよくわかりません。)

WatchKitにはiOSでいうNavigationControllerのようなものはありません。
pushControllerWithName:context:を実行するだけでpush画面遷移できます。iOSに慣れているとなんだか不思議。

SecondInterfaceController.swift

import WatchKit
import Foundation

class SecondInterfaceController: WKInterfaceController {
    
    // 初期化処理
    override init(context: AnyObject?) {
        super.init(context: context)
        NSLog("%@ init context:%@", self, context as String)
    }
    
    // pushThirdボタン押下時の処理
    @IBAction func onPushThirdButtonPushed() {
        pushControllerWithName("thirdInterfaceControllerId", context: "gotoThird!!")
    }
    
    // popボタン押下時の処理
    @IBAction func onPopButtonPushed() {
        popController()
    }
}

init実行のタイミング、つまりFirstInterfaceControllerのpushSecondボタン押下時に以下の様なログが表示されます。

2014-12-03 23:52:43.805 WatchKitSample WatchKit Extension[12014:2877844] <WatchKitSample_WatchKit_Extension.SecondInterfaceController: 0x7fe99c200080> init context:gotoSecond!!

contextで値を受け渡せていることがわかりますね。

popController()を実行すると前の画面に戻ることができます。
ただ、popで画面遷移すると、iOSのNavigationControllerのように自動的に左上に戻るボタンが表示されます。
戻るボタンは自分で実装する必要はなさそうです。

ThirdInterfaceController.swift

import WatchKit
import Foundation

class ThirdInterfaceController: WKInterfaceController {
    
    override init(context: AnyObject?) {
        super.init(context: context)
        NSLog("%@ init context:%@", self, context as String)
    }
    
    @IBAction func onPopButtonPushed() {
        popController()
    }
    
    @IBAction func onPopToRootButtonPushed() {
        popToRootController()
    }
}

popToRootController()を実行すると、FirstInterfaceControllerに戻れます。
一気に戻れるので便利ですね。

まとめ

以上、push画面遷移の解説でした。とっても普通。

宣伝

Swiftでカスタムキーボードアプリ作りました。買ってね!
特殊文字キーボード


WatchKitで文字入力UIのTextInputControllerを表示する

この投稿はWatchKit Advent Calendar 2014の3日目の記事です。

Apple Watchで文字入力できるの?

Apple Watchを紹介しますの動画の2:19あたりにこんな映像があります。

textinputcontroller

このUIで以下の方法で文字入力出来そうです.
* リストから文字列を選ぶ
* Smart Replies (なんだこれ)
* 音声入力

リファレンス

今回もリファレンスを確認してWKInterfaceControllerに怪しいAPIがありました。

WKInterfaceController

WKInterfaceController Class Reference

以下の2つのAPIが関係ありそうです。

メソッド名がいかにも..

実装

早速実装してみました。
StoryboardでWKInterfaceControllerの画面にボタンを2つ配置し、
ボタン押下時の処理を以下のようにしました。
これで、片方のボタン押下時には文字入力のUIが表示されて、
もう片方のボタン押下時には文字入力のUIが閉じるはず!

@IBAction func onOpenButtonPushed() {
    presentTextInputControllerWithSuggestions(["aaa", "bbb", "ccc"], completion: {(str) -> Void in println(str)} )
}

@IBAction func onCloseButtonPushed() {
    dismissTextInputController()
}

実行結果

ボタンを押しても何も起きませんでした。

たすけてStack Overflow

The presentTextInputControllerWithSuggestions:completion: method of WKInterfaceController is not currently supported in iOS Simulator.
swift – Text Input Controller WatchKit – Stack Overflow

…はい。

宣伝

Swiftでカスタムキーボードアプリ作りました。買ってね!
特殊文字キーボード


WatchKitでHandoff

この投稿はWatchKit Advent Calendar 2014の2日目の記事です。

前提知識

Watch Appの画面

以下の3種類があります。
WatchKitのページの中央あたりに写真が載っています。

WatchKit Apps

アプリの画面です。WKInterfaceControllerクラスを継承して作成します。

Glances

read-onlyな情報を提示する画面です。WKInterfaceControllerクラスを継承して作成します。

Actionable Notifications

通知画面です。WKUserNotificationInterfaceControllerクラスを継承して作成します。WKUserNotificationInterfaceControllerクラスはWKInterfaceControllerクラスを継承しています。

Handoffとは

HandoffはiOS8およびOSXv10.10で新たに導入された機能で、同じユーザに結びつけられた複数のデバイス間で、操作内容(アクティビティ)を引き継ぐことができます。
(via Handoffプログラミングガイド)

WatchKitでHandoff

そもそも、そんなことできるの?

WatchKit – Apple Developerの動画の25:20あたりでUse Handoff APIと書かれているのできっと使えるはず!

今すぐためせる?

後述しますが、一部シミュレータで試せます。

リファレンスを読み解く

リファレンスを確認すると、WKInterfaceControllerにそれっぽいAPIがありました。
(長いのでとりあえず動かしたい人は、”実装してみる”まで読み飛ばしてOKです。)

WKInterfaceController

WKInterfaceController Class Reference

以下の2つのAPIがHandoffに関係ありそうです。

actionForUserActivity:context:

リファレンス

リファレンス日本語訳

Return Value
  • ルートのInterfaceControllerのかわりに表示する画面のInterfaceControllerのIdentifier。
  • nilを返すとルートのInterfaceControllerが表示される。
Discussion
  • このメソッドを実装して、Handoffからアプリが起動された時に、どのInterfaceControllerを表示するかをWatchKitに伝えるために使用します。
  • Glanceをタップすると、Glanceに対応するアプリが起動します。
  • 通常であれば、ルートのInterfaceControllerが表示されます。
  • しかし、GlanceでupdateUserActivity:userInfo:メソッドを呼び出すと、システムが起動サイクルの早期に他のInterfaceControllerのIdentifier※を返すチャンスをくれます。
  • もし、Glanceが有効なIdentifierを返したら、システムはルートの画面の代わりにIdentifierに対応するInterfaceControllerを表示します。
  • このメソッドのデフォルト実装は何もしません。このメソッドをオーバーライドするとき、superを呼び出してはいけません。

※ IdentifierはStoryboardで設定します。

解説

GlanceでupdateUserActivity:userInfo:メソッドを実行すると、アプリ起動時に表示する画面を切り替えられるようになります。
userInfoに好きな情報を入れられるので、アプリ起動時に任意の画面でuserInfoの情報を表示することができそうです。

updateUserActivity:userInfo:

リファレンス

リファレンス日本語訳

Discussion
  • Glanceでは、このメソッドを呼び出して、表示している内容についての情報をuserInfoとして提供してください。ユーザがGlanceをタップするとアプリにコンテキスト情報が送信されます。
  • このメソッドを呼び出すと、アプリにアクティビティを送信します。システムがiPhoneに情報を配信し、iPhoneが他のデバイスに情報を伝播します。
  • InterfaceControllerのコード実行中はいつでもこのメソッドを呼び出すことができる。

解説

メソッド名の通り、アクティビティの更新ができるようです。

実装してみる

前置きが長くなりましたが、いよいよWatchKitでHandoffを使うアプリを実装してみましょう。
リファレンスを読み解いた結果、2種類のパターンがあることがわかりました。

Storyboard

以下のようになってるとします。(XcodeのスクリーンショットはNDAなので…)
storyboard

[パターン1] iPhoneのアクティビティをAppleWatchで継続する場合

Glanceからアプリを起動します。
このパターンはシミュレータで動くことを確認できます。

処理の流れ

以下の様な流れになります。

p1

  1. GlanceでupdateUserActivity:userInfo:を実行する。
  2. ユーザがGlanceをタップする。
  3. Firstに実装したactionForUserActivity:context:がアプリ起動時に表示したいInterfaceControllerのIdentifier(つまりSecondのIdentifier)を返す。
  4. システムがIdentifierに対応するInterfaceController(つまりSecond)を表示する。

GranceController.swift

どこかでupdateUserActivity:userInfo:を実行します。
今回は、initで実行しました。

override init(context: AnyObject?) {
    super.init(context: context)
    NSLog("%@ init", self)    
    updateUserActivity("net.haranicle.watchHandoff.sample", userInfo: ["hoge":"piyo"])
}

FirstInterfaceController.swift

actionForUserActivity:context:を実装して、SecondのIdentifierをreturnします。

override func actionForUserActivity(userActivity: [NSObject : AnyObject]?, context: AutoreleasingUnsafeMutablePointer<AnyObject?>) -> String? {
    NSLog("%@ actionForUserActivity", self)
    
    return "secondInterfaceControllerId"
}

これだけでGlanceをタップすると自動的にSecondが表示されるようになりました!
(一瞬Firstが表示されます。)

動かないときは…

iOSシミュレータでiCloudにログインしてみてください。
Glanceの起動方法がわからない場合はググって!(NDAこわい)

[パターン2] AppleWatchのアクティビティをiPhoneで継続する場合

先に断っておきますが、このパターンは動きませんでした。
WatchKitのバグなのか、僕のコードが悪いのかわかりませんが。
以下は予想でしかないです。間違っていたら指摘してください。

処理の流れ

p2

  1. First実行中の任意のタイミング(ボタン押下時にした)でupdateUserActivity:userInfo:を実行する。
  2. システムがiPhoneに情報(アクティビティ)を配信し、iPhoneが他のデバイスに情報を伝播します。
  3. ユーザがiOSデバイスのロック画面のHandoffからiOSアプリを起動します。
  4. HandoffでiOSアプリ起動時にで継続の処理(application:willContinueUserActivityWithType)が実行されます。

FirstInterfaceController.swift

ボタン押下時にupdateUserActivity:userInfo:を実行しましたが、ボタン押下時にWatch画面に回るポンデリングが表示されっぱなしになりました。
なにか間違ってるんでしょうか..? このパターンの検証は諦めました。

@IBAction func onButtonPushed() {
    updateUserActivity("net.haranicle.watchHandoff.buttonPushed", userInfo: ["hoge":"piyo"])
}

まとめ

今回は[パターン1]iPhoneのアクティビティをAppleWatchで継続する場合はWatchKitでもHandoffが動くことがわかりました。
早く実機でためしたい!!

参考

Getting started with Handoff

宣伝

Swiftでカスタムキーボードアプリ作りました。買ってね!
特殊文字キーボード


potatitips#11でiPhoneだけでデバッグする方法について発表してきた

potatitips#11

無謀にもpotatitips#11で発表してきました。
iOSの分は@yimajoさんが丁寧にまとめてくれています。

クックパッド社で #potatotips 第11回に参加してきた!あのApple Watch用SDKのTipsもあったよ! | 株式会社キュリオシティソフトウェア

(さらに…)


YosemiteにしたらCocoaPodsがうごかなくなった

エラーメッセージ

$ pod install
/System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/ruby/2.0.0/fileutils.rb:245:in `mkdir': Permission denied - /Library/Ruby/Gems/2.0.0/extensions/universal-darwin-14 (Errno::EACCES)
	from /System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/ruby/2.0.0/fileutils.rb:245:in `fu_mkdir'
	from /System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/ruby/2.0.0/fileutils.rb:219:in `block (2 levels) in mkdir_p'
	from /System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/ruby/2.0.0/fileutils.rb:217:in `reverse_each'
	from /System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/ruby/2.0.0/fileutils.rb:217:in `block in mkdir_p'
	from /System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/ruby/2.0.0/fileutils.rb:203:in `each'
	from /System/Library/Frameworks/Ruby.framework/Versions/2.0/usr/lib/ruby/2.0.0/fileutils.rb:203:in `mkdir_p'
	from /Library/Ruby/Site/2.0.0/rubygems/ext/builder.rb:210:in `write_gem_make_out'
	from /Library/Ruby/Site/2.0.0/rubygems/ext/builder.rb:132:in `build_error'
	from /Library/Ruby/Site/2.0.0/rubygems/ext/builder.rb:171:in `rescue in build_extension'
	from /Library/Ruby/Site/2.0.0/rubygems/ext/builder.rb:156:in `build_extension'
	from /Library/Ruby/Site/2.0.0/rubygems/ext/builder.rb:198:in `block in build_extensions'
	from /Library/Ruby/Site/2.0.0/rubygems/ext/builder.rb:195:in `each'
	from /Library/Ruby/Site/2.0.0/rubygems/ext/builder.rb:195:in `build_extensions'
	from /Library/Ruby/Site/2.0.0/rubygems/specification.rb:1436:in `block in build_extensions'
	from /Library/Ruby/Site/2.0.0/rubygems/user_interaction.rb:45:in `use_ui'
	from /Library/Ruby/Site/2.0.0/rubygems/specification.rb:1434:in `build_extensions'
	from /Library/Ruby/Site/2.0.0/rubygems/stub_specification.rb:60:in `build_extensions'
	from /Library/Ruby/Site/2.0.0/rubygems/basic_specification.rb:56:in `contains_requirable_file?'
	from /Library/Ruby/Site/2.0.0/rubygems/specification.rb:925:in `block in find_inactive_by_path'
	from /Library/Ruby/Site/2.0.0/rubygems/specification.rb:924:in `each'
	from /Library/Ruby/Site/2.0.0/rubygems/specification.rb:924:in `find'
	from /Library/Ruby/Site/2.0.0/rubygems/specification.rb:924:in `find_inactive_by_path'
	from /Library/Ruby/Site/2.0.0/rubygems.rb:185:in `try_activate'
	from /Library/Ruby/Site/2.0.0/rubygems/core_ext/kernel_require.rb:132:in `rescue in require'
	from /Library/Ruby/Site/2.0.0/rubygems/core_ext/kernel_require.rb:144:in `require'
	from /Library/Ruby/Gems/2.0.0/gems/xcodeproj-0.17.0/lib/xcodeproj/ext.rb:4:in `<top (required)>'
	from /Library/Ruby/Site/2.0.0/rubygems/core_ext/kernel_require.rb:55:in `require'
	from /Library/Ruby/Site/2.0.0/rubygems/core_ext/kernel_require.rb:55:in `require'
	from /Library/Ruby/Gems/2.0.0/gems/xcodeproj-0.17.0/lib/xcodeproj.rb:30:in `<top (required)>'
	from /Library/Ruby/Site/2.0.0/rubygems/core_ext/kernel_require.rb:55:in `require'
	from /Library/Ruby/Site/2.0.0/rubygems/core_ext/kernel_require.rb:55:in `require'
	from /Library/Ruby/Gems/2.0.0/gems/cocoapods-0.33.1/lib/cocoapods.rb:2:in `<top (required)>'
	from /Library/Ruby/Site/2.0.0/rubygems/core_ext/kernel_require.rb:55:in `require'
	from /Library/Ruby/Site/2.0.0/rubygems/core_ext/kernel_require.rb:55:in `require'
	from /Library/Ruby/Gems/2.0.0/gems/cocoapods-0.33.1/bin/pod:32:in `<top (required)>'
	from /usr/bin/pod:23:in `load'
	from /usr/bin/pod:23:in `<main>'

解決法

これでなおった。

$ sudo gem uninstall cocoapods && sudo gem install cocoapods

Swift1.1で黒魔術「Method Swizzling」

Method Swizzlingとは

既存のメソッドを自作のメソッドに差し替える黒魔術です。
Objective-Cで実現する方法については、こちらのページがわかりやすいです。

既存クラスのメソッドを入れ替える(Method Swizzling)

Swiftで試す

Swiftでもclass_getInstanceMethod()とmethod_exchangeImplementations()が動くので
単純に書き換えるだけで動きました。
以下は、UIViewControllerのviewDidLoadで試したコードです。
viewDidLoadのタイミングでログを吐くようにしています。

extension UIViewController {
    func loggingViewDidLoad() {
        println("\(self.dynamicType) viewDidLoad")
    }
    
    class func switchLoggingMethod () {
        let fromMethod = class_getInstanceMethod(UIViewController.self, "viewDidLoad")
        let toMethod = class_getInstanceMethod(UIViewController.self, "loggingViewDidLoad")
        method_exchangeImplementations(fromMethod, toMethod);
    }
}

ポイント

extension

Swiftにはカテゴリがないので、extensionで実現しています。やってることはObjective-Cのカテゴリと同じです。

class_getInstanceMethod

第2引数がただの文字列です。ここが一番はまりました。
ちなみにSwift1.0では以下のような書き方になるようです。

let fromMethod = class_getInstanceMethod(UIViewController.self, Selector.convertFromStringLiteral("viewDidLoad"))

すべてのクラスで使えるわけではない

これ重要。
NSObjectを継承していないクラスでは使用できません。

参考

Swift funtime

サンプルコード

haranicle/SwiftMethodSwizzling


FMDBで使うSQLiteをSQLCipherで暗号化する

アプリで使用するSQLiteは書き換えられる

iFunBoxというツールを使用すると簡単にアプリで生成したファイルを閲覧したり、編集できたりしてしまいます。
iFunBox for Windows | File Manager, Browser, Explorer, Transferer for iPhone, iPad and iPod Touch

こちらに詳しく書かれています。
iPhoneアプリの初歩的なデータ書き換え・チート方法と、それを防ぐ方法 | Lancork

FMDB + SQLCipher でSQLite暗号化

SQLCipherというSQLite暗号化ライブラリを使用すると、SQLiteのファイルを暗号化し
閲覧・編集できないようにできます。
FMDBでSQLiteを使用している場合は、SQLCipherを簡単に組み込むことができます。

SQLCipherは有料で結構なお値段しちゃいます。
Buy SQLCipher – Zetetic

でも、SQLCipher Community Editionであれば、無料だし、BSDライセンスで利用できます。
SQLCipher – Zetetic

SQLCipher Community Editionは自分でビルドしないといけませんが、俺達にはCocoaPodsがついてるんだぜ。

FMDB + SQLCipher 導入方法

CocoaPods

Podfileに以下を追記

pod 'FMDB/SQLCipher'

ターミナルでpod install

$pod install

Project設定

project_settings

Project > Build Settings > Other C Flagsに “-DSQLITE_HAS_CODEC” を追記

以上で導入完了です。

使い方

ほぼ通常のFMDBと同様ですが、毎回FMDatabaseをOpenした直後に
setKeyで暗号化のキーをセットしてあげてください。

FMDatabase* db = [FMDatabase databaseWithPath:@"path/to/dbfile"];
NSString* sql = @"INSERT INTO nums (num) VALUES (?)";
[db open];
[db setKey:@"dbfile_encrypt_key"]; // 暗号化のキーを設定
[db executeUpdate:sql, [NSString stringWithFormat:@"%ld", num]];
[db close];

サンプルコード

haranicle/DadamoreApp
のFmdbDaoを見てください。
ビルドする前にpod installしてください。

ハマりポイント

FmdbDaoの@propertyにFMDatabase*型のインスタンスを持たせて、
いろんなメソッド(INSERTするメソッドとかSELETするメソッドとか)で使いまわしていたら、
以下の様なエラーが出て、DBにアクセスできませんでした。

26 "file is encrypted or is not a database"

各メソッドで毎回FMDatabase#databaseWithPathして、setKeyをするようにしたら
正常にDBにアクセスできて、暗号化も動作するようになりました。

参考

FMDB with SQLCipher Tutorial | Guilmo