2013-04-19
【訂正】プロの力を身につける iPhone/iPadアプリケーション開発の教科書
「プロの力を身につける iPhone/iPadアプリケーション開発の教科書」の中に誤りがありましたので訂正させていただきます。
3-3 データを活用したアプリの作り方
235ページ下から4行目
訂正前
次のように、UILabel と UITextField
オブジェクトを配置します。Object Libraryから「Label」と「TextField」を選んで、ストーリーボードの「Detail」と表示されているシーンの上に配置してください。
訂正後
次のように、UIScrollView、UILabel 、UITextField オブジェクトを配置します。Object Libraryから「Scroll View」「Label」「Text Field」を選んで、ストーリーボードの「Detail」と表示されているシーンの上に配置してください。
UIScrollViewについての記述が抜けていました。
237ページ最終行
訂正前
次のように、UILabel と UITextField オブジェクトを配置してください
訂正後
次のように、UIScrollView、UILabel 、UITextField オブジェクトを配置してください。
235ページと同じく、UIScrollViewについての記述が抜けていました。
今後、間違いを見つけ次第この記事に追記していきます。
間違いがないように編集者さんと何度もレビューをしてチェックをしたつもりでしたがもれがありました。この場を借りで読者の皆様にお詫び申し上げます。また間違いを指摘してくださいました読者様ありがとうございました。
今後も本書の間違いや訂正があればこの記事に追記して行きます。
2013-04-07
NSManagedObjectでTo-Many関連を使用したときにコードの自動生成がされないバグの解決方法
Core Data の NSManagedObject クラスで以下のように To-Many(One-To-Many) 関連を使ったときにコードの自動生成をすると実装コードが生成されない場合があります。

通常の To-Many 関連では問題が起こらないのですが以下のように Ordered(順序) を指定してから NSManagedObject クラスのサブクラスの自動生成をすると実装コード(.m ファイルのコード)の一部が生成されません。
これは Xcode のバグみたいです。最新の Xcode 4.6.1 でも発生しています。
自動生成されないコードはプログラマ自身が実装する必要があります。以下は Transaction(取引) クラスと Journal(仕訳明細) クラスに Ordered の To-Many 関連を設定する場合のコードの実装例です。
#import "Transaction.h" #import "Journal.h" @implementation Transaction @dynamic abstract; @dynamic date; @dynamic entrepreneurNumber; @dynamic journals; static NSString *const kItemsKey = @"journals"; - (void)insertObject:(Journal *)value inJournalsAtIndex:(NSUInteger)idx { NSIndexSet* indexes = [NSIndexSet indexSetWithIndex:idx]; [self willChange:NSKeyValueChangeInsertion valuesAtIndexes:indexes forKey:kItemsKey]; NSMutableOrderedSet *tmpOrderedSet = [NSMutableOrderedSet orderedSetWithOrderedSet:[self mutableOrderedSetValueForKey:kItemsKey]]; [tmpOrderedSet insertObject:value atIndex:idx]; [self setPrimitiveValue:tmpOrderedSet forKey:kItemsKey]; [self didChange:NSKeyValueChangeInsertion valuesAtIndexes:indexes forKey:kItemsKey]; } - (void)removeObjectFromJournalsAtIndex:(NSUInteger)idx { NSIndexSet* indexes = [NSIndexSet indexSetWithIndex:idx]; [self willChange:NSKeyValueChangeRemoval valuesAtIndexes:indexes forKey:kItemsKey]; NSMutableOrderedSet *tmpOrderedSet = [NSMutableOrderedSet orderedSetWithOrderedSet:[self mutableOrderedSetValueForKey:kItemsKey]]; [tmpOrderedSet removeObjectAtIndex:idx]; [self setPrimitiveValue:tmpOrderedSet forKey:kItemsKey]; [self didChange:NSKeyValueChangeRemoval valuesAtIndexes:indexes forKey:kItemsKey]; } - (void)insertJournals:(NSArray *)values atIndexes:(NSIndexSet *)indexes { [self willChange:NSKeyValueChangeInsertion valuesAtIndexes:indexes forKey:kItemsKey]; NSMutableOrderedSet *tmpOrderedSet = [NSMutableOrderedSet orderedSetWithOrderedSet:[self mutableOrderedSetValueForKey:kItemsKey]]; [tmpOrderedSet insertObjects:values atIndexes:indexes]; [self setPrimitiveValue:tmpOrderedSet forKey:kItemsKey]; [self didChange:NSKeyValueChangeInsertion valuesAtIndexes:indexes forKey:kItemsKey]; } - (void)removeJournalsAtIndexes:(NSIndexSet *)indexes { [self willChange:NSKeyValueChangeRemoval valuesAtIndexes:indexes forKey:kItemsKey]; NSMutableOrderedSet *tmpOrderedSet = [NSMutableOrderedSet orderedSetWithOrderedSet:[self mutableOrderedSetValueForKey:kItemsKey]]; [tmpOrderedSet removeObjectsAtIndexes:indexes]; [self setPrimitiveValue:tmpOrderedSet forKey:kItemsKey]; [self didChange:NSKeyValueChangeRemoval valuesAtIndexes:indexes forKey:kItemsKey]; } - (void)replaceObjectInJournalsAtIndex:(NSUInteger)idx withObject:(Journal *)value { NSIndexSet* indexes = [NSIndexSet indexSetWithIndex:idx]; [self willChange:NSKeyValueChangeReplacement valuesAtIndexes:indexes forKey:kItemsKey]; NSMutableOrderedSet *tmpOrderedSet = [NSMutableOrderedSet orderedSetWithOrderedSet:[self mutableOrderedSetValueForKey:kItemsKey]]; [tmpOrderedSet replaceObjectAtIndex:idx withObject:value]; [self setPrimitiveValue:tmpOrderedSet forKey:kItemsKey]; [self didChange:NSKeyValueChangeReplacement valuesAtIndexes:indexes forKey:kItemsKey]; } - (void)replaceJournalsAtIndexes:(NSIndexSet *)indexes withJournals:(NSArray *)values { [self willChange:NSKeyValueChangeReplacement valuesAtIndexes:indexes forKey:kItemsKey]; NSMutableOrderedSet *tmpOrderedSet = [NSMutableOrderedSet orderedSetWithOrderedSet:[self mutableOrderedSetValueForKey:kItemsKey]]; [tmpOrderedSet replaceObjectsAtIndexes:indexes withObjects:values]; [self setPrimitiveValue:tmpOrderedSet forKey:kItemsKey]; [self didChange:NSKeyValueChangeReplacement valuesAtIndexes:indexes forKey:kItemsKey]; } - (void)addJournalsObject:(Journal *)value { NSMutableOrderedSet *tmpOrderedSet = [NSMutableOrderedSet orderedSetWithOrderedSet:[self mutableOrderedSetValueForKey:kItemsKey]]; NSUInteger idx = [tmpOrderedSet count]; NSIndexSet* indexes = [NSIndexSet indexSetWithIndex:idx]; [self willChange:NSKeyValueChangeInsertion valuesAtIndexes:indexes forKey:kItemsKey]; [tmpOrderedSet addObject:value]; [self setPrimitiveValue:tmpOrderedSet forKey:kItemsKey]; [self didChange:NSKeyValueChangeInsertion valuesAtIndexes:indexes forKey:kItemsKey]; } - (void)removeJournalsObject:(Journal *)value { NSMutableOrderedSet *tmpOrderedSet = [NSMutableOrderedSet orderedSetWithOrderedSet:[self mutableOrderedSetValueForKey:kItemsKey]]; NSUInteger idx = [tmpOrderedSet indexOfObject:value]; if (idx != NSNotFound) { NSIndexSet* indexes = [NSIndexSet indexSetWithIndex:idx]; [self willChange:NSKeyValueChangeRemoval valuesAtIndexes:indexes forKey:kItemsKey]; [tmpOrderedSet removeObject:value]; [self setPrimitiveValue:tmpOrderedSet forKey:kItemsKey]; [self didChange:NSKeyValueChangeRemoval valuesAtIndexes:indexes forKey:kItemsKey]; } } - (void)addJournals:(NSOrderedSet *)values { NSMutableOrderedSet *tmpOrderedSet = [NSMutableOrderedSet orderedSetWithOrderedSet:[self mutableOrderedSetValueForKey:kItemsKey]]; NSMutableIndexSet *indexes = [NSMutableIndexSet indexSet]; NSUInteger valuesCount = [values count]; NSUInteger objectsCount = [tmpOrderedSet count]; for (NSUInteger i = 0; i < valuesCount; ++i) { [indexes addIndex:(objectsCount + i)]; } if (valuesCount > 0) { [self willChange:NSKeyValueChangeInsertion valuesAtIndexes:indexes forKey:kItemsKey]; [tmpOrderedSet addObjectsFromArray:[values array]]; [self setPrimitiveValue:tmpOrderedSet forKey:kItemsKey]; [self didChange:NSKeyValueChangeInsertion valuesAtIndexes:indexes forKey:kItemsKey]; } } - (void)removeJournals:(NSOrderedSet *)values { NSMutableOrderedSet *tmpOrderedSet = [NSMutableOrderedSet orderedSetWithOrderedSet:[self mutableOrderedSetValueForKey:kItemsKey]]; NSMutableIndexSet *indexes = [NSMutableIndexSet indexSet]; for (id value in values) { NSUInteger idx = [tmpOrderedSet indexOfObject:value]; if (idx != NSNotFound) { [indexes addIndex:idx]; } } if ([indexes count] > 0) { [self willChange:NSKeyValueChangeRemoval valuesAtIndexes:indexes forKey:kItemsKey]; [tmpOrderedSet removeObjectsAtIndexes:indexes]; [self setPrimitiveValue:tmpOrderedSet forKey:kItemsKey]; [self didChange:NSKeyValueChangeRemoval valuesAtIndexes:indexes forKey:kItemsKey]; } } @end
Xcode の次のバージョンで修正されると良いのですが...。
関連書籍
Core Dataに関する基礎情報はこちらの本を参考にしてください。
参考
2013-02-26
本日発売!プロの力を身につける iPhone/iPadアプリケーション開発の教科書
ついにこの日がやってきました。
「プロの力を身につける iPhone/iPadアプリケーション開発の教科書」本日発売です。
先ほど職場近くの「MARUZEN & JUNKUDO」さんに行ってみたらバッチリ置いてありました。感無量です。読者の皆様のアプリ開発助けになれば幸いです。よろしくお願いします。
本の詳しい内容はこちらの記事をご覧下さい。
電子書籍でも読めます
「プロの力を身につける iPhone/iPadアプリケーション開発の教科書」は電子書籍でも読むことが出来ます。こちらの方が値段が少しお得です。
レビューしていただきました。
パンダピアノで有名なアプリ開発者のムラモトタケシさんにレビューをしていただきました。ありがとうございます!
うちの娘(2歳11ヶ月)も大好きでよく遊んでます。
レビューしていただきました。その2
有名ブロガーの拡張現実ライフさんにレビューしていただきました。ありがとうございます!
ゲーム関係の話題が好きで良く読んでます。
- Twitter / @pasela
- Twitter / @24appnet
- Twitter / @glassonion1
- Twitter / @HM_heroes
- Twitter / @chun_ryo
- Twitter / @kazuhito
- Twitter / @glassonion1
- Twitter / @FloatingOTT
- Twitter / @merika427
- Twitter / @samura1_net
- Twitter / @glassonion1
- Twitter / @HM_heroes
- Twitter / @HM_heroes
- Twitter / @youten_redo
- Twitter / @Nexus_K
- Twitter / @glassonion1
- Twitter / @7roid
- Twitter / @shiraco
- Twitter / @glassonion1
- Twitter / @rohinomiya
- Twitter / @glassonion1
2013-02-10
iOSアプリ開発の本を書きました
ご無沙汰しております。約半年ぶりの記事です。
この間、ずっと iOS アプリ開発の本を書いていました。タイトルは「プロの力を身につける iPhone/iPadアプリケーション開発の教科書」です。
出版社様から書籍執筆のお話を頂いたのが約1年半前でした。やっと発売にこぎつけることができました。2月26日発売です。早ければ22日から書店に並ぶと思います。
本の内容のいくつかは当ブログの記事が元になっています。iOS アプリ開発をはじめられたばかりの方からバリバリ開発している方まで幅広く読める内容になっています。魂込めて書いた自信作ですのでよろしくお願いします。
発売に先駆けて「プロの力を身につける iPhone/iPadアプリケーション開発の教科書」の目次を紹介させていただきます(ついでに元になったブログ記事へのリンクをはっておきます。中身検索的な感じでお使いください)。
iPhone/iPadアプリケーション開発の教科書 ー目次ー
第1章 Objective-Cとオブジェクト指向プログラミング
- 1-1 Objective-C入門
Obejctive-C 3分クッキングを元に新規作成
- 1-2 オブジェクト指向プログラミングのコツ
クラスとオブジェクトの違い、スタックとヒープとオブジェクト - 1-3 メモリ管理とオブジェクト指向
オーナーシップポリシーとARC
第2章 iOSアプリの画面開発の基礎を理解する
- 2-1 画面開発の基礎知識
iOS アプリの画面開発の基礎を理解するを加筆修正
- 2-2 iOSのイベント駆動をライフサイクルイベントとユーザアションイベントに分けて理解する
iOS のイベント駆動をライフサイクルイベントとユーザアクションイベントにわけて理解するを加筆修正
- 2-3 テーブルビューとコレクションビューを使ったアプリの作り方
UITableViewの使い方と iOS から追加されたUICollectionViewの使い方
第3章 データを活用したアプリの作り方
- 3-1 データ設計入門
iOS データ設計入門を加筆修正
- 3-2 メモリ上にデータを保存する方法
UIPasteboard の使い方とメモリ上のデータを複数画面で共有する方法 - 3-3 iOSでデータを永続化する方法
iOS でデータを永続化する方法を加筆修正
- iOSでオブジェクトをシリアライズしてファイルに保存する方法
iOS でオブジェクトをシリアライズしてファイルに保存する方法を加筆修正
- iOS でプロパティリストを使ったデータの保存方法
iOS でプロパティリストを使ったデータの保存方法を加筆修正
- NSUserDefaults を使ったデータの保存方法
NSUserDefaults を使ったデータの保存方法を加筆修正
- Core Data入門
サルでもわかる Core Data 入門【概念編】
サルでもわかる Core Data 入門【実装編】を加筆修正
- iOSでオブジェクトをシリアライズしてファイルに保存する方法
- 3-4 iCloudにデータを保存する方法 ~iCloudプログラミング入門~
iCloud プログラミング入門を加筆修正
第4章 応用編
- 4-1 iOSアプリの構造がどのようになっているか紐解いてみる
iOS アプリの構造がどのようになっているか紐解いてみるを加筆修正
- 4-2 マルチスレッドプログラミング入門
NSOperationQueue スレッドと処理の関係を元に新規作成
- 4-3 iOSユニットテスト入門
SenTestCase で非同期処理のテストをする方法を元に新規作成
- 4-4 In-App Purchaseを使ったアイテム課金プログラミング
失敗しない iOS In-App Purchase プログラミングを加筆修正
Appendix1 メモリ管理詳細
- A1-1 メモリ管理で気をつけること
iOSアプリ開発のメモリ管理で気をつけることを加筆修正
Appendix2 アドレス帳アプリのソースコードから学ぶ
- A2-1 理解度チェックコード
コラム(すべて新規作成)
- クラスとプロトコル(インターフェース)
- クラス図とオブジェクト図
- Xcode 入門
- MVC
- IBOutlet と IBAction
- バー専用のUI部品クラス
- NSLog と文字列フォーマット
- エンティティからクラスを作成する
電子書籍でも読めます
「プロの力を身につける iPhone/iPadアプリケーション開発の教科書」は電子書籍でも読むことが出来ます。こちらの方が値段が少しお得です。
謝辞
執筆にあたり出版元のソフトバンククリエイティブ様や当ブログの読者様など多くの方のお世話になりました。この場をお借りしてお礼申し上げます。
- Twitter / @HM_heroes
- Twitter / @takobouzu00
- Twitter / @mnishikawa
- Twitter / @himuralight
- Twitter / @Keita_Shiya
- Twitter / @glassonion1
- Twitter / @cou_z
- Twitter / @iaki2627
- Twitter / @FloatingOTT
- Twitter / @pasela
- Twitter / @takeshi_75
- Twitter / @fakestarbaby
- Twitter / @ShinHANAHiro
- Twitter / @HagemaruA
- Twitter / @hiranodept
- Twitter / @glassonion1
- Twitter / @glassonion1
- Twitter / @glassonion1
- Twitter / @murajun1978
- Twitter / @yuppy1221
- Twitter / @napoleon_na
- Twitter / @glassonion1
- Twitter / @KINBO
- Twitter / @sho49cc
- Twitter / @hnyssh
- Twitter / @glassonion1
- Twitter / @u_akihiro
- Twitter / @HM_heroes
- Twitter / @HIDEKIT5095
- Twitter / @glassonion1
- Twitter / @HM_heroes
- Twitter / @hdakym2t
- Twitter / @flarephoenix
- Twitter / @yukifurukawa
- Twitter / @WatenShindou
- Twitter / @WatenShindou
2012-07-28
iCloud プログラミング入門
「iOS データ設計入門」でデータはメモリ、フラッシュドライブ、iCloud に保存することができると説明しました。今回は iCloud にデータを保存して複数端末でデータを共有する方法について説明していきます。
iCloud って何?
クラウドと聞くとなにやら難しい感じがしますが、iCloud を簡単にいうとインターネット上にあるデータの置き場です。実体はものすごい数のサーバです。

iCloud のデータは Apple ID ごとに管理されていて同じ ID であれば iPhone iPad iMac など端末を問わずデータを共有することが出来ます。
この記事は iCloud のプログラミングについて説明するので「iCloud って何って?」ところには深入りしません。iCloud に関する詳しい説明は下記書籍を参照してください。
iCloud を使うと何がうれしいのか?
iCloud は以下の図のように、端末がネットワークにつながっていればユーザが意識することなく自動でデータを同期して複数端末でデータを共有することができます。
これによりユーザはアプリの利用場面に応じて端末を選ぶことが出来ます。外出先は iPhone、自宅は iPad、職場は iMac、みたいなことが簡単にできるのです。iCloud に対応したアプリを開発する場合、ユニバーサルアプリ(iPhone, iPod Touch, iPad に対応したアプリ)化することをおすすめします。ユニバーサル対応したアプリの方がより iCloud のメリットを活かすことが出来ます。
iCloud を使ってデータを共有する方法
アプリから iCloud を使ってデータを共有するには以下の3つの方法があります。
- Key-value storage
Key-value 形式でデータを保存する。使い方は簡単だが保存できる容量が 1MB に制限されている。電子書籍アプリのしおりやブラウザアプリのブックマーク、アプリの設定などちょっとしたデータを保存するのに向いている - Document storage
ドキュメント形式で iCloud にデータを保存する - Core Data storage
Core Data のデータを iCloud を使って共有する。大量かつ複雑なデータの保存に向いている
本記事では上記のうち Key-value storage と Core Data storage の使い方を紹介します。ここで記事で使用する iCloud 関連のライブラリは iOS と Mac OS X で使うことが出来ます。
iCloud を使うための設定
アプリから iCloud を使うためには、プロビジョニングポータルページ(https://developer.apple.com/ios/manage/overview/index.action)の App ID とアプリのプロジェクトファイルを修正する必要があります。
プロビジョニングポータルの設定
- Provisioning Portal の「App IDs」ページを開いて、サンプルアプリに使用する App ID の Configure リンクをクリックします。

- App ID の詳細画面で「Enable for iCloud」にチェックを入れてページ下部の「Done」ボタンを押します。

- 「Provisioning」ページを開いて、先ほど編集した App ID を使っているプロビジョニングプロファイルの「Renew」 ボタンを押して更新します。
- プロビジョニングプロファイルをダウンロードしてダウンロードしたファイルをダブルクリックします。
プロジェクトファイルの設定
- プロジェクトからターゲットを選択して「Summary」タブの一番下にある「Entitlements」の「Enable Entitlements」にチェックを入れます。自動的に「Keychain Access Groups」に Bundle Identifier の値が1行追加されます。

- 「iCloud Key-Value Store」にチェックを入れて「iCloud Key-Value Store」に Bundle Identifier の値を入力します。この設定は「Key-value storage」を使う時に行います

- 「iCloud Containers」の「+」ボタンをクリック1行追加します。自動的に Bundle Identifier(Company ID + プロジェクト名)の値が入ります。この設定は「Document storage」または「Core Data storage」を使う時に行います

iPhone iPad の設定
端末側の設定も必要です。設定アプリの「iCloud > 書類とデータ」をオンにします。画面は以下の通りです。
以上で iCloud を使う準備は完了です。
iCloud Key-value storageを使う方法
Key-value storage を使って iCloud とデータを同期化するには NSUbiquitousKeyValueStore オブジェクトを使用します。このオブジェクトは名前の通りキーバリュー形式でデータを保存してくれます。データを保存すると一旦端末のフラッシュドライブにデータを保存しておき、いい感じのタイミングで iCloud に同期してくれます。NSUbiquitousKeyValueStore オブジェクトを使ったデータの保存と取得処理は NSUserDefaults オブジェクトの使い方とかなり似ています。キーバリュー形式でデータを保存する方法の詳細は以下の記事を参照してください。
データの保存と取得で使用するメソッド
NSUbiquitousKeyValueStore クラスにはデータ型に応じて以下の保存と取得メソッドが定義されています。
| オブジェクトの型 | 保存メソッド | 取得メソッド |
|---|---|---|
| オブジェクト全般(id型) | setObject: forKey: | objectForKey: |
| NSString | setString: forKey: | stringForKey: |
| NSArray | setArray: forKey: | arrayForKey: |
| NSDictionary | setDictionary: forKey: | dictionaryForKey: |
| NSData | setData: forKey: | dataForKey: |
| long long | setLongLong: forKey: | longLongForKey: |
| double | setDouble: forKey: | doubleForKey: |
| BOOL | setBool: forKey: | boolForKey: |
データの保存
それでは具体的に NSUbiquitousKeyValueStore オブジェクトを使ってデータを保存する方法を見ていきましょう。defaultStore メソッドを使って NSUbiquitousKeyValueStore オブジェクトを取得してからデータを保存します。
以下は setLongLong: forKey: メソッドを使った数値データの保存の例です。
NSUbiquitousKeyValueStore *ukvs = [NSUbiquitousKeyValueStore defaultStore];
[ukvs setLongLong:index forKey:@"pageNumber"];
[ukvs synchronize];
データの取得
NSUbiquitousKeyValueStore に保存されているデータを取得する方法を見てみましょう。以下は longLongForKey: メソッドを使った数値データの取得の例です。
NSUbiquitousKeyValueStore *ukvs = [NSUbiquitousKeyValueStore defaultStore];
NSUInteger index = [ukvs longLongForKey:@"pageNumber"];
データの同期
他の端末から iCloud のデータが変更されると NSUbiquitousKeyValueStoreDidChangeExternallyNotification という名前で通知が送信されます。Key-value data storage のデータを画面に表示している場合は以下のように UIViewController の viewDidLoad メソッドで通知を受け取る設定をします。
- (void)viewDidLoad { [super viewDidLoad]; // iCloud からデータの変更通知を受ける設定 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(ubiquitousDataDidChange:) name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification object:nil]; : : 省略 : }
受け取った通知には以下のようなデータがわたってきます。
userInfo = {
NSUbiquitousKeyValueStoreChangeReasonKey = 1;
NSUbiquitousKeyValueStoreChangedKeysKey = (
bookmark,
pageNumber
);
}}
NSUbiquitousKeyValueStoreChangeReasonKey にはどのような理由でデータが変更されたかがわかる enum 値が格納されています。また NSUbiquitousKeyValueStoreChangedKeysKey には変更があったデータのキー情報が配列で格納されています。通知データの詳細は以下のページを参照してください。
変更が発生したデータは以下のようにキーごとに for 文でまわして取得します。ちなみに通知処理はメインスレッドで実行されます。
- (void)ubiquitousDataDidChange:(NSNotification *)notification { // 通知データ NSDictionary *dict = [notification userInfo]; NSLog(@"%@", dict); // 変更されたデータのキー値 NSArray *keys = [dict objectForKey:NSUbiquitousKeyValueStoreChangedKeysKey]; NSUbiquitousKeyValueStore *ukvs = [NSUbiquitousKeyValueStore defaultStore]; for (NSString *key in keys) { // 変更が発生したデータの取得 NSUInteger index = [ukvs longLongForKey:key]; NSLog(@"index:%d", index); } : : データの同期処理、画面の再表示など : }
iCloud Core Data storage を使う方法
Core Data storage を使って iCloud とデータを同期するには Core Data のセットアップ処理で iCloud 用のオプション設定を追加する必要があります。
サルでもわかる Core Data 入門【実装編】で作成したアドレス帳アプリのサンプルコードを拡張して iCloud に対応させてみます。
これから紹介するサンプルプログラムは GCD(Grand Central Dispatch) や Blocks を使った非同期処理が多く全体的にコードの難易度が高めです。GCD や Blocks に関する詳しい説明は下記書籍を参考にしてください。おすすめです。
AppDelegate クラスの修正
はじめに managedObjectContext メソッドを変更します。
NSManagedObjectContext オブジェクトを生成して、生成したオブジェクトに NSPersistentStoreCoordinator オブジェクトをセットします。iCloud を使うと managedObjectContext メソッドがメインスレッド以外から呼び出される可能性があるため NSManagedObjectContext オブジェクトの performBlockAndWait: メソッドを使って NSPersistentStoreCoordinator オブジェクトのセットと通知の設定をメインスレッドで行います*1。
- (NSManagedObjectContext *)managedObjectContext { if (_managedObjectContext != nil) { return _managedObjectContext; } NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator]; if (coordinator != nil) { NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType]; [moc performBlockAndWait:^{ [moc setPersistentStoreCoordinator:coordinator]; // iCloud からデータの変更通知を受ける設定 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(mergeChangesFrom_iCloud:) name:NSPersistentStoreDidImportUbiquitousContentChangesNotification object:coordinator]; }]; _managedObjectContext = moc; } return _managedObjectContext; }
次に persistentStoreCoordinator メソッドを変更します。NSFileManager オブジェクトの URLForUbiquityContainerIdentifier メソッドを使って iCloud と同期するフォルダのパスを取得します。このメソッドは処理に数秒間かかる可能性があるため dispatch_async 関数を使って非同期で実行します。取得した iCloud のパスは NSPersistentStoreCoordinator オブジェクトの addPersistentStoreWithType: configuration: URL: options: error: メソッドにオプションとして渡します。
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator { if (_persistentStoreCoordinator != nil) { return _persistentStoreCoordinator; } NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"AddressBook.sqlite"]; _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]]; __weak NSPersistentStoreCoordinator *psc = _persistentStoreCoordinator; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSMutableDictionary *options = [@{NSMigratePersistentStoresAutomaticallyOption : @(YES), NSInferMappingModelAutomaticallyOption : @(YES)} mutableCopy]; NSURL *cloudURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil]; if (cloudURL) { // iCloud 用の設定 cloudURL = [cloudURL URLByAppendingPathComponent:@"data"]; [options setValue:@"AddressBook.store" forKey:NSPersistentStoreUbiquitousContentNameKey]; [options setValue:cloudURL forKey:NSPersistentStoreUbiquitousContentURLKey]; } NSError *error = nil; [psc lock]; if (![psc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error]) { NSLog(@"Unresolved error %@, %@", error, [error userInfo]); abort(); } [psc unlock]; dispatch_async(dispatch_get_main_queue(), ^{ // Core Data のセットアップが終わったことを通知する [[NSNotificationCenter defaultCenter] postNotificationName:@"RefetchAllDatabaseData" object:self userInfo:nil]; }); }); return _persistentStoreCoordinator; }
最後に mergeChangesFrom_iCloud: メソッドを新たに作成して処理を追加します。他の端末でデータを保存するなど iCloud のデータに変更があった場合、NSPersistentStoreDidImportUbiquitousContentChangesNotification という通知が送信されます。mergeChangesFrom_iCloud: メソッドはそのときに実行されるメソッドです。このメソッドはメインスレッドで実行されないため NSManagedObjectContext オブジェクトの performBlock: メソッドを使って処理をメインスレッドで実行します。
- (void)mergeChangesFrom_iCloud:(NSNotification *)notification { __weak NSManagedObjectContext* moc = [self managedObjectContext]; [moc performBlock:^{ [moc mergeChangesFromContextDidSaveNotification:notification]; }]; }
MasterViewController クラスの修正
viewDidLoad メソッドに Core Data のセットアップ終了通知を受け取る処理を追加します。
- (void)viewDidLoad { [super viewDidLoad]; : : 省略 : // 通知を受け取る設定 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reloadFetchedResults:) name:@"RefetchAllDatabaseData" object:nil]; }
reloadFetchedResults: メソッドを新たに作成してデータとテーブルの再読み込み処理を追加します。
- (void)reloadFetchedResults:(NSNotification*)notification { NSError *error = nil; if (![[self fetchedResultsController] performFetch:&error]) { NSLog(@"Unresolved error %@, %@", error, [error userInfo]); abort(); } [self.tableView reloadData]; }
動作確認
それではここまで作成したアプリの動作を確認しましょう。
まずはアプリに何件か適当にデータを入れて一旦アプリを削除します。再度アプリをインストールして起動した時に前のデータがちゃんと残っているか確認します。
次に同じ Apple ID の端末を2台用意します。1台目のアプリのデータを変更したときに、2台目のアプリにそのデータの変更が反映されたかどうかを確認します*2。
iCloud に保存されたデータを消去したい時は
プログラムを試行錯誤して作成していると無駄なデータが iCloud に溜まっていきます。iCloud のデータを削除する時は設定アプリの「iCloud > ストレージとバックアップ > ストレージを管理 > 書類およびデータ」で削除することができます。画面は以下の通りです。
サンプルコード
Key-value storage を使ったサンプルコード(UIPageViewController を使ったカレンダーにブックマークを設定するサンプル)
Core Data storage を使ったサンプルコード(Address Book の拡張)
関連記事
参考
- About Incorporating iCloud Into Your App
- iCloud Storage
- iCloudに対応したiOSアプリを作ってみよう:iCloud基礎編
- Using Core Data with iCloud Release Notes
- iOS How-To : Using Core Data with iCloud
- How to check if iCloud is configured programmatically
参考書籍
- Twitter / @cou_z
- Twitter / @cou_z
- Twitter / @glassonion1
- Twitter / @glassonion1
- Twitter / @kaz225
- Twitter / @kaz225
- Twitter / @fakestarbaby
- Twitter / @ppworks
- Twitter / @fakestarbaby
- Twitter / @jitte
- Twitter / @glassonion1
- Twitter / @glassonion1
- Twitter / @fakestarbaby
- Twitter / @fakestarbaby
- Twitter / @tomooco
- Twitter / @glassonion1
- Twitter / @wp_
- Twitter / @mstMINAMI
- Twitter / @glassonion1
- Twitter / @uchiruda
- Twitter / @cou_z
- Twitter / @glassonion1
- Twitter / @pei29
- Twitter / @tpyamamoto
- Twitter / @pei29
- Twitter / @glassonion1
- Twitter / @fakestarbaby
- Twitter / @glassonion1
- Twitter / @yakisake
- Twitter / @glassonion1
- Twitter / @fakestarbaby
- Twitter / @glassonion1
- Twitter / @EnnaMika
- Twitter / @es_dees
- Twitter / @glassonion1
- Twitter / @Tuka421
- Twitter / @glassonion1
- Twitter / @fakestarbaby
- Twitter / @glassonion1
- Twitter / @sk44_
- Twitter / @glassonion1
- Twitter / @100nasu
- Twitter / @Shtr28
- Twitter / @castell_81
- Twitter / @glassonion1
- Twitter / @glassonion1
- Twitter / @OolongBreaker
- Twitter / @glassonion1
- Twitter / @castell_81
- Twitter / @u_akihiro
- Twitter / @anrdlove
- Twitter / @shin_en42
- Twitter / @glassonion1
- Twitter / @a_kaneda
- Twitter / @glassonion1
- Twitter / @fakestarbaby
- Twitter / @glassonion1
- Twitter / @shiraco
- Twitter / @glassonion1
- Twitter / @hirota2kuwayama
- Twitter / @glassonion1
- Twitter / @sk44_
- Twitter / @glassonion1
- Twitter / @xthe_worldx
- Twitter / @glassonion1
- Twitter / @mskinjo
- Twitter / @nakaji_dayo
- Twitter / @glassonion1
- Twitter / @yukihariguchi
- Twitter / @glassonion1
- Twitter / @nuvler
- Twitter / @glassonion1
- Twitter / @kojiokb
- Twitter / @glassonion1
- Twitter / @penguindaa
- Twitter / @pattun_lover
- Twitter / @kazutomi
- Twitter / @glassonion1
- Twitter / @dentomo
- Twitter / @glassonion1
- Twitter / @nakamura001
- Twitter / @Judgement203
- Twitter / @glassonion1
- Twitter / @mochiz
- Twitter / @break_througher
- Twitter / @glassonion1
- Twitter / @u_akihiro
- Twitter / @morikuma7
- Twitter / @glassonion1
- Twitter / @nagakenjs
- Twitter / @you_and_i
- Twitter / @mayusaki3
- Twitter / @kyoumiattara












