Hatena::ブログ(Diary)

A Day In The Life RSSフィード Twitter

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) 関連を使ったときにコードの自動生成をすると実装コードが生成されない場合があります。

one-to-many関連

通常の To-Many 関連では問題が起こらないのですが以下のように Ordered(順序) を指定してから NSManagedObject クラスのサブクラスの自動生成をすると実装コード(.m ファイルのコード)の一部が生成されません。

Orderedにチェックを入れる

これは 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

有名ブロガー拡張現実ライフさんにレビューしていただきました。ありがとうございます!

ゲーム関係の話題が好きで良く読んでます。

2013-02-10

iOSアプリ開発の本を書きました

ご無沙汰しております。約半年ぶりの記事です。

この間、ずっと iOS アプリ開発の本を書いていました。タイトルは「プロの力を身につける iPhone/iPadアプリケーション開発の教科書」です。

出版社様から書籍執筆のお話を頂いたのが約1年半前でした。やっと発売にこぎつけることができました。2月26日発売です。早ければ22日から書店に並ぶと思います。

本の内容のいくつかは当ブログの記事が元になっています。iOS アプリ開発をはじめられたばかりの方からバリバリ開発している方まで幅広く読める内容になっています。魂込めて書いた自信作ですのでよろしくお願いします。

発売に先駆けて「プロの力を身につける iPhone/iPadアプリケーション開発の教科書」の目次を紹介させていただきます(ついでに元になったブログ記事へのリンクをはっておきます。中身検索的な感じでお使いください)。

iPhone/iPadアプリケーション開発の教科書 ー目次ー

第1章 Objective-Cオブジェクト指向プログラミング
第2章 iOSアプリの画面開発の基礎を理解する
第3章 データを活用したアプリの作り方
第4章 応用編
Appendix1 メモリ管理詳細
Appendix2 アドレス帳アプリソースコードから学ぶ
  • A2-1 理解度チェックコード
コラム(すべて新規作成)
  • クラスとプロトコル(インターフェース)
  • クラス図とオブジェクト図
  • Xcode 入門
  • MVC
  • IBOutlet と IBAction
  • バー専用のUI部品クラス
  • NSLog と文字列フォーマット
  • エンティティからクラスを作成する

電子書籍でも読めます

「プロの力を身につける iPhone/iPadアプリケーション開発の教科書」は電子書籍でも読むことが出来ます。こちらの方が値段が少しお得です。

謝辞

執筆にあたり出版元のソフトバンククリエイティブ様や当ブログの読者様など多くの方のお世話になりました。この場をお借りしてお礼申し上げます。

2012-07-28

iCloud プログラミング入門

iOS データ設計入門」でデータはメモリ、フラッシュドライブ、iCloud に保存することができると説明しました。今回は 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 関連のライブラリiOSMac OS X で使うことが出来ます。

iCloud を使うための設定

アプリから iCloud を使うためには、プロビジョニングポータルページ(https://developer.apple.com/ios/manage/overview/index.action)の App ID とアプリのプロジェクトファイルを修正する必要があります。

プロビジョニングポータルの設定
  1. Provisioning Portal の「App IDs」ページを開いて、サンプルアプリに使用する App ID の Configure リンクをクリックします。
    手順1-1
  2. App ID の詳細画面で「Enable for iCloud」にチェックを入れてページ下部の「Done」ボタンを押します。
    手順1-2
  3. 「Provisioning」ページを開いて、先ほど編集した App ID を使っているプロビジョニングプロファイルの「Renew」 ボタンを押して更新します。
  4. プロビジョニングプロファイルをダウンロードしてダウンロードしたファイルをダブルクリックします。
プロジェクトファイルの設定
  1. プロジェクトからターゲットを選択して「Summary」タブの一番下にある「Entitlements」の「Enable Entitlements」にチェックを入れます。自動的に「Keychain Access Groups」に Bundle Identifier の値が1行追加されます。
    手順2-1
  2. iCloud Key-Value Store」にチェックを入れて「iCloud Key-Value Store」に Bundle Identifier の値を入力します。この設定は「Key-value storage」を使う時に行います
    手順2-2
  3. iCloud Containers」の「+」ボタンをクリック1行追加します。自動的に Bundle Identifier(Company ID + プロジェクト名)の値が入ります。この設定は「Document storage」または「Core Data storage」を使う時に行います
    手順2-3
iPhone iPad の設定

端末側の設定も必要です。設定アプリの「iCloud > 書類とデータ」をオンにします。画面は以下の通りです。

iCloud の設定

以上で iCloud を使う準備は完了です。

iCloud Key-value storageを使う方法

Key-value storage を使って iCloud とデータを同期化するには NSUbiquitousKeyValueStore オブジェクトを使用します。このオブジェクトは名前の通りキーバリュー形式でデータを保存してくれます。データを保存すると一旦端末のフラッシュドライブにデータを保存しておき、いい感じのタイミングで iCloud に同期してくれます。NSUbiquitousKeyValueStore オブジェクトを使ったデータの保存と取得処理は NSUserDefaults オブジェクトの使い方とかなり似ています。キーバリュー形式でデータを保存する方法の詳細は以下の記事を参照してください。

データの保存と取得で使用するメソッド

NSUbiquitousKeyValueStore クラスにはデータ型に応じて以下の保存と取得メソッドが定義されています。

オブジェクトの型保存メソッド取得メソッド
オブジェクト全般(id型)setObject: forKey:objectForKey:
NSStringsetString: forKey:stringForKey:
NSArraysetArray: forKey:arrayForKey:
NSDictionarysetDictionary: forKey:dictionaryForKey:
NSDatasetData: forKey:dataForKey:
long longsetLongLong: forKey:longLongForKey:
doublesetDouble: forKey:doubleForKey:
BOOLsetBool: 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 > ストレージバックアップ > ストレージを管理 > 書類およびデータ」で削除することができます。画面は以下の通りです。

iCloud のデータ削除

サンプルコード

Key-value storage を使ったサンプルコード(UIPageViewController を使ったカレンダーにブックマークを設定するサンプル)

Core Data storage を使ったサンプルコード(Address Book の拡張)

関連記事

参考

参考書籍

*1:NSManagedObjectContext オブジェクトスレッドセーフではなく単一スレッドで使用されることが想定されているためこのような処理が必要になります

*2検証完了しました問題なく動きます