一日一度だけ何かする処理のメモ

パズドラをやった事はないですが、
一日一度だけアラートを表示する等の処理を、
UserDefaultsで作成してみたのでメモ。
(20131218微妙に修正)

1.起動時にアラート表示日を保存
2.アラート表示日がなければアラート表示
3.アラート表示日が前回表示日と変わっていたらアラート表示

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    // アラート表示確認
    NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
    if ([ud valueForKey:@"showAlertAt"])
    {
        // 前回アラート表示日があり、日付が変わっていればアラート再表示
        NSString *saveDateStr = [ud valueForKey:@"showAlertAt"];
        if ([saveDateStr compare:[self str_today_yyyyMMdd]] != NSOrderedSame)
        {
            NSLog(@"アラート起動処理");
        }
    }
    else
    {
        // アラート表示日がなければ初回起動なのでアラート表示
        NSLog(@"アラート起動処理");
        [ud setObject:[self str_today_yyyyMMdd] forKey:@"showAlertAt"];
        [ud synchronize];
    }
}

/*!今日の日付をyyyyMMdd文字列で返す */
- (NSString *)str_today_yyyyMMdd
{
    // 今日の日付を作る、GMT+-0のTimeZoneをセットしユーザー設定による影響を回避
    NSDate *date = [NSDate date];
    NSDateFormatter *df = [[NSDateFormatter alloc] init];
    [df setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
    [df setDateFormat:@"yyyy/MM/dd"];
    
    return [df stringFromDate:date];
}

非形式プロトコルのメモ

非形式プロトコルのメモ、あえて非形式を使う利点はなさそうだった
今は形式プロトコルでも@optionalで指定すれば実装は任意にできる。
実装を強制する場合は@requiredで指定する。
その方が明示的でわかりやすい
Objective-Cプログラミング言語(Apple公式)


Counter.h 非形式プロトコルを宣言するクラス

#import <Foundation/Foundation.h>

@interface Counter : NSObject
@property(nonatomic) id delegate;
- (void)count;
- (id)initWithDelegate:(id)delegate;
@end



Counter.m 非形式プロトコルを宣言するクラス
10数えたらdelegateに処理を委譲

#import "Counter.h"

// 非形式プロトコル Informal Protocol
// NSObjectのカテゴリとして宣言
@interface NSObject(CounterDelegate)
- (void)didFinishCountUpToTen:(Counter *)counter;
@end

@implementation Counter

- (id)initWithDelegate:(id)delegate
{
    self = [super init];
    if (self) {
        _delegate = delegate;
    }
    return self;
}

// 数を数えるメソッド
- (void)count
{
    static int nowCnt = 0;
    nowCnt++;
    // 10まで数えたら通知
    if (nowCnt >= 10) {
        if ([[self delegate] respondsToSelector:@selector(didFinishCountUpToTen:)]) {
            [self.delegate didFinishCountUpToTen:self];
        }
    }
}
@end



Player.h 非形式プロトコルを実装するクラス

#import <Foundation/Foundation.h>
@interface Player : NSObject
- (void)play;
@end



Player.m

#import "Player.h"
#import "Counter.h"
@implementation Player

// Counterを初期化して10回数える
- (void)play{
    Counter *counter  = [[Counter alloc] initWithDelegate:self];
    for (int i = 0; i < 10; i++) {
        [counter count];
    }
}

// 非形式プロトコルのメソッドを実装
- (void)didFinishCountUpToTen:(Counter *)counter
{
    NSLog(@"10回数え終わったよ!");
}
@end



main.m 実行確認用

#import <Foundation/Foundation.h>
#import "Player.h"
int main(int argc, const char * argv[])
{
    @autoreleasepool {
        Player *player  = [[Player alloc] init];
        [player play];
    }
    return 0;
}


データを管理するクラスを作ってみる。

基本かもしれないですが、UITableViewController内で使うデータアクセスのコードは、
データ管理クラスを間に挟むと、データ詳細を意識しなくて良いので使いやすくなります。


例えば、画面にこんな感じに魚データを表示する場合。



DataManagerというクラスを作って、そいつにデータを持たせます。
データのRead等はManagerにやらせます。


というわけで以下のような感じでコードを書いてみます。
"魚"を表すFishクラス
"データを管理する"DataManagerクラス
データ管理クラスを使いデータを表示するテーブルビューコントローラサブクラス


DataManager.h

#import <Foundation/Foundation.h>
#import "Fish.h"

@interface DataManager : NSObject

// 他のクラスからアクセスできるように魚群を公開
@property(nonatomic,strong) NSArray *fishes;
@end



DataManager.m

#import "DataManager.h"
#import "Fish.h"

// データを管理するクラス
@implementation DataManager

// 初期化します、シングルトンにしたければしてください。
- (id)init
{
    self = [super init];
    if (self) {
        [self readData];
    }
    return self;
}

// データ読みこんで保持します
- (void)readData{
    NSArray *fishArray = @[@"さけ",@"さば",@"さめ",@"あざらし"];
    self.fishes = fishArray;
}
@end



Fish.h

#import <Foundation/Foundation.h>
// 魚くんクラス
@interface Fish : NSObject

@property(nonatomic,copy) NSString *name;
@property(nonatomic) NSInteger width;

@end



#import "Fish.h"

@implementation Fish

- (id)init
{
    self = [super init];
    if (self) {
        // デフォ値をいれたり、いれなかったりします
        _name = @"名無しのさかなさん";
        _width = 0;
    }
    return self;
}

@end



データ管理クラスを使うテーブルビューコントローラのサブクラス
MyTVC.m

#import "MyTVC.h"
#import "DataManager.h"
@interface MyTVC ()

@end

@implementation MyTVC
{
    DataManager *_dtManager;
}

- (void)viewDidLoad
{
    [super viewDidLoad];

    // データマネージャーの準備、今回はここ、本来必要な所で初期化する
    _dtManager = [[DataManager alloc] init];
}
// 行の数
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    // 行の数
    return _dtManager.fishes.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
    
    // Configure the cell...
    cell.textLabel.text = [_dtManager.fishes objectAtIndex:indexPath.row];
    return cell;
}

ターゲットを追加時にdyld`dyld_fatal_errorでクラッシュ

既存のXcodeプロジェクトにストターゲットを追加したらエラーでクラッシュした。

dyld`dyld_fatal_error:
0x8fe18070: int3
0x8fe18071: nop

dyld: lazy symbol binding failed: Symbol not found: _objc_setProperty_nonatomic_copy



【原因】
ターゲットを追加するとデフォルトでios Deployment Targetが最新のiOS6.0になる。
この場合ターゲット以下、例えばiOS5.1等で実行するとシンボルの結合に失敗しエラーが発生する。


【解決法】
TARGETS > テストターゲット > Build Settings > ios Deployment Targetを
シミュレータのiOSバージョンより下げてあげればOK
ターゲットの追加時は気をつけましょう。


参考:
http://stackoverflow.com/questions/12686009/program-run-at-ios5-1-device-cant-find-sdk-at-xcode-4-5

MagicalRecordのREADMEを意訳

MagicalRecordのREADMEの抜粋を意訳しつつメモ。
原文のREADMEはこちら


※20140930 MR_contextForCurrentThreadとMR_SHORTHANDが3.0で廃止される事を追記

逐次githubのissuesをご確認下さい。



目次

インストール方法

参考:MagicalRecord2.0を使えるように準備する

  1. XcodeプロジェクトにMagicalRecordフォルダをドラッグしてね。
  2. CoreData+MagicalRecord.hをpchファイルかAppDelegateに追加してね
  3. MagicalRecordのメソッドを「MR_findAll」でなくて「findAll」みたく「MR_」なしで使いたい時は#define MR_SHORTHANDをpchファイルに追加してね。*1
  4. コードを書き始めよう!


必要条件

iOS4互換バージョンが使用可能。参照:tag 1.8.3


いつ保存すれば良いか

もし保存に長時間かかっていた場合、検討すべき点がいくつかあります。

  1. バックグラウンドスレッドでの保存

MagicalRecordではエンティティの変更から保存をバックグラウンドスレッドで処理する為、
シンプルでクリーンなAPIを提供しています、例えば以下のように記述できます。

[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
    // 引数localContextインスタンスに対して保存する変更を行う
    //  ブロック内の処理はバックグラウンドスレッドで発生します

} completion:^(BOOL success, NSError *error) {
    [application endBackgroundTask:bgTask];
    bgTask = UIBackgroundTaskInvalid;
}];
  1. 小さい保存処理になるようタスクを細分化します

大量のデータのインポートのようなタスクは小さなチャンクに分割します。
どの場合でもどんなデータ量でもフィットするタスクのサイズはないので(?訳が違うかもです..)
AppleのInstrumentsのようなツールでパフォーマンスを計測する必要があります。


長時間の保存のハンドリング

iOS
When an application terminates on iOS, it is given a small window of opportunity to tidy up and save any data to disk. If you know that a save operation is likely to take a while, the best approach is to request an extension to your application's expiration, like so:

iOSアプリの終了時はデータを整理し保存する為のわずかな機会が与えられます。
もし保存処理がしばらくかかる事が分かっている場合、良いアプローチは以下のようにアプリケーションの存命期間の延長を要求することです。

UIApplication *application = [UIApplication sharedApplication];

__block UIBackgroundTaskIdentifier bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
    [application endBackgroundTask:bgTask];
    bgTask = UIBackgroundTaskInvalid;
}];

[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
    // Do your work to be saved here

} completion:^(BOOL success, NSError *error) {
    [application endBackgroundTask:bgTask];
    bgTask = UIBackgroundTaskInvalid;
}];

事前に必ずbeginBackgroundTaskWithExpirationHandlerについてのドキュメントを読んで下さい、
不適切、又は、不要なアプリケーションの生存期間の延長はリジェクトされる可能性があります。


保存処理の変更

保存の為のAPIは、より一貫的な動作をするように改訂されており、
CoreDataフレームワーク命名パターンに沿ったものとなりました。
広範な作業は将来のアップデートを通じて、
saveメソッド(新規および非推奨の両方)が期待通り動作するよう自動化されたテストを追加することでした。


MR_saveは、今の所はカレントスレッドで同期的に実行されているものを元の状態に復元し、永続ストアに保存しています。
ただし、__ MR_saveメソッドは非推奨としてマークされ、MagicalRecordの次のメジャーリリース(バージョン3.0)_で削除される予定です。
ライブラリの将来のバージョンで同じ動作をしたい場合は、
MR_saveToPersistentStoreAndWaitを使用する必要があります。


新しいメソッド

以下のメソッドが新しく追加されました。

NSManagedObjectContext+MagicalSaves

  • - (void) MR_saveOnlySelfWithCompletion:(MRSaveCompletionHandler)completion;
  • - (void) MR_saveToPersistentStoreWithCompletion:(MRSaveCompletionHandler)completion;
  • - (void) MR_saveOnlySelfAndWait;
  • - (void) MR_saveToPersistentStoreAndWait;
  • - (void) MR_saveWithOptions:(MRSaveContextOptions)mask completion:(MRSaveCompletionHandler)completion;



MagicalRecord+Actions

  • + (void) saveWithBlock:(void(^)(NSManagedObjectContext *localContext))block;
  • + (void) saveWithBlock:(void(^)(NSManagedObjectContext *localContext))block completion:(MRSaveCompletionHandler)completion;
  • + (void) saveWithBlockAndWait:(void(^)(NSManagedObjectContext *localContext))block;
  • + (void) saveUsingCurrentThreadContextWithBlock:(void (^)(NSManagedObjectContext *localContext))block completion:(MRSaveCompletionHandler)completion;
  • + (void) saveUsingCurrentThreadContextWithBlockAndWait:(void (^)(NSManagedObjectContext *localContext))block;



廃止予定のメソッド

以下のメソッドは新しい代替手段が支持された事により、非推奨にされています。
これらのメソッドはMagicalRecord 3.0で廃止されます。

NSManagedObjectContext+MagicalSaves

  • - (void) MR_save;
  • - (void) MR_saveWithErrorCallback:(void(^)(NSError *error))errorCallback;
  • - (void) MR_saveInBackgroundCompletion:(void (^)(void))completion;
  • - (void) MR_saveInBackgroundErrorHandler:(void (^)(NSError *error))errorCallback;
  • - (void) MR_saveInBackgroundErrorHandler:(void (^)(NSError *error))errorCallback completion:(void (^)(void))completion;
  • - (void) MR_saveNestedContexts;
  • - (void) MR_saveNestedContextsErrorHandler:(void (^)(NSError *error))errorCallback;
  • - (void) MR_saveNestedContextsErrorHandler:(void (^)(NSError *error))errorCallback completion:(void (^)(void))completion;

MagicalRecord+Actions

  • + (void) saveWithBlock:(void(^)(NSManagedObjectContext *localContext))block;
  • + (void) saveInBackgroundWithBlock:(void(^)(NSManagedObjectContext *localContext))block;
  • + (void) saveInBackgroundWithBlock:(void(^)(NSManagedObjectContext *localContext))block completion:(void(^)(void))completion;
  • + (void) saveInBackgroundUsingCurrentContextWithBlock:(void (^)(NSManagedObjectContext *localContext))block completion:(v

目次に戻る

ARC Support

MagicalRecordはARCを完全にサポートしているので何も構成する必要はありません。
手動メモリ管理をサポートする最後のバージョンは1.8.3であり、ダウンロードページから、
またはソースの1.8.3タグに切り替えることで利用可能です。

目次に戻る

■ 注釈

Third Party Blog Entries..(全ブログ英語)


ログ出力

MagicalRecordは全てのフェッチ要求とCoreDataの処理をロギングします。
データのフェッチ、保存時にエラーが発生した場合、これらのエラーはMagicalRecordによってキャプチャされます。デフォルトでログはNSLogを使用しています。
CocoaLumberjackをインストールしている場合は、MagicalRecordはCocoaLumberjackを使用し適切な出力にログを送信する設定です。
MagicalRecord内のすべてのログは、CoreData+ MagicalRecord.hのインポートの前に、
プリプロセッサ文を定義することで無効にできます。

#define MR_ENABLE_ACTIVE_RECORD_LOGGING 0

目次に戻る

■ 使用方法

CoreDataスタックの設定

まず、プロジェクトのpchファイルにhファイルCoreData+ MagicalRecord.hをインポートします。
これで全ての必要なヘッダーをグローバルにインクルードできるようになります。
次にapplicationDidFinishLaunchingメソッド、またはawakeFromNib内に、
MagicalRecordクラスと以下のsetupメソッドのいずれかを使用します。

+ (void) setupCoreDataStack;
+ (void) setupAutoMigratingDefaultCoreDataStack;
+ (void) setupCoreDataStackWithInMemoryStore;
+ (void) setupCoreDataStackWithStoreNamed:(NSString *)storeName;
+ (void) setupCoreDataStackWithAutoMigratingSqliteStoreNamed:(NSString *)storeName;

上記の呼び出しは、Core Dataスタックの構成部品をインスタンス化しgetterとsetterを提供します。
インスタンスはMagicalRecordのwell-known-instance(既知のインスタンス)になり、
"デフォルト値"として認識されます。
(MR_defaultManagedObjectModelやMR_defaultContext等でアクセス可能になる。)


DEBUGフラグが設定されたデフォルトのsqliteのデータ·ストアを使用する場合、
新しいモデルバージョンを作成せずに、モデルを変更した場合、MagicalRecordでは、
古いストアを削除し自動的に新しいものを作成します。
変更を行うたびにアンインストール/再インストールをする事はもうありません!


そしてアプリケーションを終了する前にclean-upメソッドを使用することができます。

[MagicalRecord cleanUp];

目次に戻る


iCloudサポート

iOS5以降とOSXのLion10.7.2以降用に構築されたアプリは、
CoreDataストアを同期するiCloudを活用することができます。
MagicalRecordでこの機能を実装するには、前のセクションに記載されている呼び出しの代わりに、
以下のsetupメソッドを呼び出します。

+ (void) setupCoreDataStackWithiCloudContainer:(NSString *)icloudBucket localStoreNamed:(NSString *)localStore;
+ (void) setupCoreDataStackWithiCloudContainer:(NSString *)containerID contentNameKey:(NSString *)contentNameKey localStoreNamed:(NSString *)localStoreName cloudStorePathComponent:(NSString *)pathSubcomponent;
+ (void) setupCoreDataStackWithiCloudContainer:(NSString *)containerID contentNameKey:(NSString *)contentNameKey localStoreNamed:(NSString *)localStoreName cloudStorePathComponent:(NSString *)pathSubcomponent completion:(void(^)(void))completion;



iCloudの詳細とアプリケーションがiCloudに適合していることの保証についてはApple's iCloud Notesを参照してください。
(参考:iCloud設計ガイド)

特に注目すべきは最初のヘルパーメソッドで、
アプリケーションのバンドルIDに基いて、自動的にNSPersistentStoreUbiquitousContentNameKeyを生成します。

+ (void) setupCoreDataStackWithiCloudContainer:(NSString *)icloudBucket localStoreNamed:(NSString *)localStore;

複数の異なるiCloudストアを管理するような状況でcontentNameKeyを指定する場合、
他のヘルパーメソッドを使用することを推奨します。
目次に戻る

デフォルトの管理オブジェクトコンテキスト

Core Dataを使用するときは、NSManagedObjectとNSManagedObjectContextの2つのオブジェクトを扱います。
MagicalRecordはアプリ内で使用するデフォルトNSManagedObjectContextの為に単一の場所を提供します。
これは、シングルスレッドのアプリケーションには素晴らしい事です。
あなたは簡単な呼び出しで、このデフォルト·コンテキストを取得することができます。

[NSManagedObjectContext MR_defaultContext];

inContextメソッド(後述)をオーバーロードして、
特定のコンテキストに検索やフェッチ要求を行っていない場合にデフォルトコンテキストが使用されます。
デフォルト永続ストアを元にして、別スレッドの新しい管理オブジェクトコンテキストが必要な場合、
以下のメソッドを利用します。

NSManagedObjectContext *myNewContext = [NSManagedObjectContext MR_context];

このメソッドは同一のオブジェクトモデルと永続ストアを使用していますが、
メインスレッド以外のスレッドで使用する為の完全に新しいコンテキストを作成します。


ここで新しく生成した「myNewContext」をメインスレッドの、
全フェッチ要求にとってのデフォルトコンテキストに設定したい場合は以下の様にします。

[NSManagedObjectContext MR_setDefaultContext:myNewContext];



MagicalRecordは又、複数のスレッド内の管理オブジェクトコンテキストを保持するヘルパーメソッドを持っています。
これを利用すれば、呼び出し元のスレッドに関係なく正しいNSManagedObjectContextインスタンスにアクセスできすることができます。このメソッドは以下の通りです。*2

[NSManagedObjectContext MR_contextForCurrentThread];

※デフォルトコンテキストはメインスレッドを利用して生成、設定する事を推奨します。
目次に戻る


Fetching(フェッチ)

基本的なフェッチ
MagicalRecordの殆どのメソッドは、結果をNSArrayで返します。
この事から、部署エンティティと関連をもったPersonというエンティティを持っていた場合(AppleのCoreDataのドキュメントで見られるような)、全てのPersonエンティティを永続ストアから取得するには以下の様に記述します。

//このコードを動かすにはpchファイルに"#define MR_SHORTHAND"を定義する必要があります。
NSArray *people = [Person findAll];

// もしくは名前空間を利用した長いバージョンで動作します。
NSArray *people = [Person MR_findAll];

プロパティでソートされた結果を取得する場合

NSArray *peopleSorted = [Person MR_findAllSortedByProperty:@"LastName" ascending:YES];

複数のプロパティでソートされた結果を取得する場合

NSArray *peopleSorted = [Person MR_findAllSortedByProperty:@"LastName,FirstName" ascending:YES];

もしオブジェクトをデータストアから取得する一意な方法がある場合、
直接そのオブジェクトを取得する事もできます。

Person *person = [Person MR_findFirstByAttribute:@"FirstName" withValue:@"Forrest"];



Advanced Finding(高度なフェッチ)
検索をより具体的に行いたい場合、述語(NSPredicateオブジェクト)を指定する事ができます。

NSArray *departments = [NSArray arrayWithObjects:dept1, dept2, ..., nil];
NSPredicate *peopleFilter = [NSPredicate predicateWithFormat:@"Department IN %@", departments];

NSArray *people = [Person MR_findAllWithPredicate:peopleFilter];

Returning an NSFetchRequest(フェッチ要求の返却)

NSPredicate *peopleFilter = [NSPredicate predicateWithFormat:@"Department IN %@", departments];

NSFetchRequest *people = [Person MR_requestAllWithPredicate:peopleFilter];

上記の1行ずつ、それぞれについて、
NSFetchRequest、NSSortDescriptorsとシンプルなデフォルトのエラー処理スキーム(コンソールへのロギングを除く)のフルスタックが作成されます。


フェッチ要求のカスタマイズ

NSPredicate *peopleFilter = [NSPredicate predicateWithFormat:@"Department IN %@", departments];

NSFetchRequest *peopleRequest = [Person MR_requestAllWithPredicate:peopleFilter];
[peopleRequest setReturnsDistinctResults:NO];
[peopleRequest setReturnPropertiesNamed:[NSArray arrayWithObjects:@"FirstName", @"LastName", nil]];
...
NSArray *people = [Person MR_executeFetchRequest:peopleRequest];

目次に戻る


エンティティ数の検索
永続ストア内のエンティティのカウント行う事もできます。
この処理は永続ストア上で実行されます。

NSNumber *count = [Person MR_numberOfEntities];

もし述語(NSPredicate)かその他のフィルタを元にしたエンティティのカウントを取得したい場合:

NSNumber *count = [Person MR_numberOfEntitiesWithPredicate:...];

以下のメソッドはNSNumberではなく、NSUIntegerの返却に対応しています。
(通常の呼び出しにはMR_が必要な事に注意)

  • countOfEntities
  • countOfEntitiesWithContext:(NSManagedObjectContext *)
  • countOfEntitiesWithPredicate:(NSPredicate *)
  • countOfEntitiesWithPredicate:(NSPredicate *) inContext:(NSManagedObjectContext *)



集計操作

NSPredicate *prediate = [NSPredicate predicateWithFormat:@"diaryEntry.date == %@", today];
int totalFat = [[CTFoodDiaryEntry MR_aggregateOperation:@"sum:" onAttribute:@"fatColories" withPredicate:predicate] intValue];
int fattest  = [[CTFoodDiaryEntry MR_aggregateOperation:@"max:" onAttribute:@"fatColories" withPredicate:predicate] intValue];



異なるコンテキストからの検索
全ての検索、フェッチメソッドは以下の様にinContextパラメータを持っています。
All find, fetch and request methods have an inContext: method parameter

NSManagedObjectContext *someOtherContext = ...;

NSArray *peopleFromAnotherContext = [Person MR_findAllInContext:someOtherContext];
...

Person *personFromContext = [Person MR_findFirstByAttribute:@"lastName" withValue:@"Gump" inContext:someOtherContext];
...

NSUInteger count = [Person MR_numberOfEntitiesWithContext:someOtherContext];

目次に戻る


新しいエンティティの生成

エンティティの新しいインスタンスを作る必要が場合、以下のメソッドを利用するか、

Person *myNewPersonInstance = [Person MR_createEntity];

コンテキストを指定します。

NSManagedObjectContext *otherContext = ...;

Person *myPerson = [Person MR_createInContext:otherContext];

目次に戻る


エンティティの削除

1つのエンティティの削除

Person *p = ...;
[p  MR_deleteEntity];

コンテキストを指定した場合

NSManagedObjectContext *otherContext = ...;
Person *deleteMe = ...;

[deleteMe MR_deleteInContext:otherContext];

CoreDataには全てのエンティティの一括削除やトランケート操作がありません。
MagicalRecordでは一括削除の方法の1つとして以下のメソッドを提供しています。

[Person MR_truncateAll];

コンテキストを指定する場合は以下の様に記述します。

NSManagedObjectContext *otherContext = ...;
[Person MR_truncateAllInContext:otherContext];

目次に戻る


スレッド上でCoreDataの操作を実行する

MagicalRecordはスレッド処理用のバックグラウンドコンテキストを設定する、いくつかの便利なメソッドを提供します。

バックグラウンドのセーブ操作は少し違いはありますが、UIViewのアニメーションブロックメソッドにインスパイアを受けています。

  • データ保存コードを追加したblockがメインスレッド上で実行される事はありません。
  • 1つのNSManagedObjectContextが操作の為に提供されます。

例えば、PersonエンティティにfirstNameとlastNameフィールドをセットしたいという時、
バックグラウンドコンテキストをセットアップするにはMagicalRecordを以下のように利用します:

Person *person = ...;
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext){

    Person *localPerson = [person MR_inContext:localContext];

    localPerson.firstName = @"John";
    localPerson.lastName = @"Appleseed";
}];

このメソッドでは、指定されたブロックが処理実行の為に適切なコンテキストを提供します。
バックグラウンド処理の終了をデフォルトコンテキストに伝えるよう、セットアップする必要はありませんが、
変更が別のスレッドで実行されたので更新をする必要があります。

このセーブブロックが完了した後に、アクションを実行するするには、
completion block(完了ブロック)に記述します。

Person *person = ...;
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext){

    Person *localPerson = [person MR_inContext:localContext];

    localPerson.firstName = @"John";
    localPerson.lastName = @"Appleseed";

} completion:^(BOOL success, NSError *error) {

    self.everyoneInTheDepartment = [Person findAll];

}];

この完了ブロックはメインスレッド(キュー)で呼び出されるので、
UI更新のトリガとしても安全です。
目次に戻る

*1:※MR_SHORTHANDはバージョン3.0で廃止されるようなので使わない方が良さそうです。http://stackoverflow.com/questions/21985859/mr-shorthand-does-not-work

*2:MR_contextForCurrentThreadは2.3.0で非推奨にマークされ3.0で削除予定のようです。https://github.com/magicalpanda/MagicalRecord/blob/develop/Docs/Saving.md

CotEditorの正規表現スクリプトメモ(property->synthesize)

synthesizeが省略できる今となっては無用の長物ですが、
@propertyから@synthesizeを自動生成するCotEditor用のperlスクリプトを書いてあったので、
一応記載しておきます。


・対象のobjc

@property(nonatomic) BOOL isRun;
@property(nonatomic) BOOL isFinished;
@property(nonatomic) NSInteger repeatCount;
@property(nonatomic, strong) NSString *currentWorkTitle;



perlスクリプト

#!/usr/bin/perl
use strict;
use warnings;

# 全てのテキストを標準入力へ入れる
# %%%{CotEditorXInput=AllText}%%%

# 結果は現在のテキストと置換する
# %%%{CotEditorXOutput= ReplaceAllText}%%%

my @text = <STDIN>;
foreach my $line ( @text ) {
	# 先頭の@propertyを削除    A=~s/B/C/gは、A内を正規表現Bで検索してCで置換
	$line =~ s/\@property\(.+\)\s?//g;
	# 型の部分を削除
	$line =~ s/[A-Z[\w]+\s?\*?[\s|*]//g;
	# ;を削除
	$line =~ s/;//g;
	# 変数名を@synthesizeの形式に置換
	$line =~ s/([a-z|A-Z]+)\n?/\@synthesize $1 = _$1;\n/g;
	print $line;
}

・結果

@synthesize isRun = _isRun;
@synthesize isFinished = _isFinished;
@synthesize repeatCount = _repeatCount;
@synthesize currentWorkTitle = _currentWorkTitle;



あと、メソッド宣言から空実装を生成するスクリプトです。
取り敢えず引数なしのみで、空実装側に戻り値は記載されません...
・対象のメソッド宣言(objc)

- (void)start;
- (BOOL)isRun;
- (NSString *)updateSetCount;

perlスクリプト

#!/usr/bin/perl
use strict;
use warnings;

# %%%{CotEditorXInput=AllText}%%%
# %%%{CotEditorXOutput= ReplaceAllText}%%%

my @text = <STDIN>;
foreach my $line ( @text ) {
		$line =~ s/;//g;
		$line =~ s/([\-|\+]\s?\([a-zA-Z\s\*]+\)[a-zA-Z]+)\n?/$1\{\n\t\n\}\n\n/g;
		print $line;
}

・結果

- (void)start{
	
}

- (BOOL)isRun{
	
}

- (NSString *)updateSetCount{
	
}

CoreDataのエラーメモ

CoreDataのエラーメモです。

・エラーメッセージ

Cocoa error 134100

・原因
アプリ起動後のEntityの追加等で既に存在するsqliteファイルと新しいxcdatamodeldの不整合でおこる。
・解決策
アプリを長押しで削除した後、再実行


・エラーメッセージ
英: An instance of NSFetchedResultsController requires a non-nil fetchRequest and managedObjectContext
意: NSFetchedResultsControllerの初期化時にnilのfetchRequestとmanagedObjectContextを使っちゃだめですよ


・原因
NSFetchedResultsControllerの初期化時にnilのmanagedObjectContextを渡す等

・解決策

// 以下の処理を呼んでCoreDataスタックを作る
// CoreDataManagerを自作してる場合は[COREDATAMANAGER managedObjectContext];等
- (NSManagedObjectContext *)managedObjectContext {
    
    if (_managedObjectContext != nil) {
        return _managedObjectContext;
    }
    
    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
    if (coordinator != nil) {
        _managedObjectContext = [[NSManagedObjectContext alloc] init];
        [_managedObjectContext setPersistentStoreCoordinator:coordinator];
    }
    return _managedObjectContext;
}


・エラーメッセージ
英: entityForName: could not locate an NSManagedObjectModel for entity name '''
意: entityForNameメソッド: エンティティ名を見つけられなかった。
・原因
xcdatamodeldのエンティティ名がコード上と異なるかNSManagedObjectModelがnil
・解決策

// 以下の追加メソッドを呼ぶ前にmanagedObjectContextを取得、CoreDataスタックを生成しておく。
// 新しいUserを追加するメソッド内
    CDUser *user = [NSEntityDescription insertNewObjectForEntityForName:@"User"
                                                 inManagedObjectContext:_managedObjectContext];



・エラーメッセージ

The persistent cache of section information does not match the current configuration. You have illegally mutated the NSFetchedResultsController's fetch request, its predicate, or its sort descriptor without either disabling caching or using +deleteCacheWithName

・原因
NSFetchedResultsControllerの初期化時にcacheNameを指定した場合、キャッシュを削除せずNSPredicate等を変更した。
・解決策
キャッシュを利用しない(nilを指定する)又は毎回削除する。
参考:Cocoaの日々 http://cocoadays.blogspot.jp/2010/07/uisearchdisplaycontroller_15.html

// これで消すか
[NSFetchedResultsController deleteCacheWithName:@"Master"];

// 以下のようにnilを指定する。
    NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:nil];



・バグ
NSFetchedResultsController利用時でセルのデータは削除できるがセルが消えない
・原因
NSFetchedResultsControllerDelegateのdidChangeObjectが呼ばれていない
・解決策
viewDidLoadだけでdelegateを設定していると画面遷移後nilになった時呼ばれないのでviewWillAppearに書く