ページ

2010年1月30日土曜日

新刊 - XcodeによるObjective-C入門

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

本屋へ行くと新しい本が出ていた。



Snow Leopard / Xcode3.2 を対象にした一番新しい解説書。

Xcode(SDK)のインストールから始まって、その使い方、プログラミングの初歩解説など、図も多用されていてかなり丁寧にかかれている。

入門書にいいかもしれない。

2010年1月29日金曜日

BlogAssistant(11) - 複数のプロセスからCoreData を使う 〜 Queue(もどき)の導入

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

Cocoaの日々: BlogAssistant(10) CoreData - 複数のプロセスから CoreDataを扱う 〜 NSManagedObjectのリロード

前回は1つの CoreData DB(SQLite)を複数のプロセスからアクセスする際に、NSManagedObjectContextを作り直して表示更新を行うことを試みた。



その後いろいろ考えたが、やはり正規の方法から外れるのは今後何かと問題になりそうなので、CoreData DB を操作するのは基本一つのプロセスに限定することにした。ただ IPCやその他の実装を使った同期式のクライアント=サーバモデルは、大げさすぎるし運用もしづらい。そこで簡単な非同期式の連携方法である Queueモデルを採用することにした。


こうすることで CoreData DB(SQLite)へのアクセスを一元化できる。非同期なのでリアルタイムではなくなるがファイルがよほど溜まらない限りはそんなに遅延は発生しないだろう。また Queueといっても特別な仕組みを作るのではなく、特定のフォルダを用意してそこでファイルをやりとりするだけ(このフォルダが簡易 Queueの役割を果たす)。FSEventを使えば効率的に監視ができる。

- - -
まずは監視の部分は置いておいて、ファイルのやりとり部分から実装をやって行く事にする。ファイルは取り扱いが容易な plistを使うことにする。

(続く)

2010年1月28日木曜日

BlogAssistant(10) CoreData - 複数のプロセスから CoreDataを扱う 〜 NSManagedObjectのリロード

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

Cocoaの日々: BlogAssistant(9) CoreData - awakeFromInsert の使用

BlogAssistant ではSafariプラグインを使ってページをクリップした後、別の専用アプリケーションの方でそれを表示させる。データの受け渡しには CoreData を使う。こんなイメージ。




しかし、CoreData は複数のプロセスから使う様にはできていない。Apple公式のドキュメントには複数プロセスが可能であるとの記載はないし、APIや説明を読む限り複数プロセスから扱うように出来ているとは思えない(ただしマルチスレッドは OK。注意点は色々あるものの、ドキュメントにも説明が割かれている)。

Safariプラグインと、ビューアを分けてしまったものの困ったことになった。このまま無理矢理自己責任で進むか、それとも当初のように Safariプロセス内で全部やるか。うーむ。


とりあえずできるところまで別プロセスでやってみることする。今回は用途はクリティカルではないので最悪DBが壊れても困らない。


さて、その上で最初に解決しなければならないのは、Safariプラグインでクリップした時に書き出したCoreDataの変更を、別プロセスの表示に反映する方法を見つけること。

トリガーは、プラグイン側で書き出し後にビューアアプリをキックするのでいいとして、問題はその後。複数プロセスからの利用を想定していない CoreDataスタックは SQLiteが書き変わったことが検出できないのはもちろんのこと、一発で NSManagedObjectContext にデータを再読み込みさせる方法がない(見つけられなかった)。

NSManagedObjectContext には -[reset] が用意されているが、既にNSManagedObjectContext内にフェッチされていた内容がすべて消えてしまう。再フェッチは自分でやらなければいけないが NSArrayController と連携している場合にやり方がよくわからない。

※探してみると Stack Overflow に同じ目的の質問が出ていた。それに対する答えは、複数プロセスで直接データへアクセスするの無理だろう、片側のプロセスで一括して管理するようにした方がいい(クライアント-サーバモデルを取る)とのこと。
NSManagedObjectContext and NSArrayController reset/refresh problem - Stack Overflow

改めて複数プロセスからのアクセスは無謀であることがわかったが。。


いろいろ考えて思いついたのは NSManagedObjectContext を再作成すること。つまり今まで使っていたものを解放して、同じ NSPersistentCoordinator から新たに NSManagedObjectContext を作成する。そしてそれを NSArrayController へ再設定してやる。

実際のコードはこんな感じ。

CoreDataManager.m

- (NSManagedObjectContext*)recreateContext
{
if (managedObjectContext) {
[managedObjectContext release];
managedObjectContext = nil;
}
return [self managedObjectContext];
}


表示を最新に更新する場合はこれを呼び出して、戻り値で取得した新しい NSManagedObjectContext をビューが参照している NSArrayController へ設定してやれば良い。

コード例:

-(IBAction)reset:(id)sender
{
[arrayController setManagedObjectContext:
[[CoreDataManager sharedManager] recreateContext]];
}



実際に試したところうまく動いた(更新された)。


- - - -
やっておいて何だが、少々荒技でかなりの無駄遣いな方法だと思う。特に件数が多い場合は NSManagedObjectContextを作り直すたびに全件読み込みになるので時間とメモリを使う。メモリは気になるので後で Instruments で調べてみたい。

2010年1月27日水曜日

BlogAssistant(9) CoreData - awakeFromInsert の使用

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

(前回)Cocoaの日々: BlogAssistant(8) - Safariプラグインとビューアの分離

モデルへ新たに作成日時を保持する createdDate を追加した。


現在日時を入れることがわかっているので、NSManagedObjectを新規に作成した時には自動的にセットした。

そういった目的のために NSManagedObject には -[awakeFromInsert] が用意されている。
Mac Dev Center: NSManagedObject Class Reference - awakeFromInsert

NSManagedObject が新規に作成される時に呼び出される。サブクラスでこれをオーバーライドすると初期化処理を書く事ができる。

こんな感じ。

- (void) awakeFromInsert {
    [super awakeFromInsert];
    [self setPrimitiveValue:[NSDate date] forKey:@"createdDate"];
}

上記では createdDate に現在日時をセットしている。


この awakeFromInsert には利用上いくつか注意点がある。
  • 必ず先頭で親クラスの awakeFromInsert を呼び出す
  • [1]作成 => [2]Undo(作成取り消し) => [3]Redo(再作成) の操作を行った場合、[3]では呼び出されない
  • メソッド内で fetch操作を行うと副作用が出る(場合がある)
  • primitive accessor (setPrimitiveValue:forKey:など) を使うのが望ましい。これは Undo/Redoが正しく働かせるため。
最後の点について、通常 NSManagedObject のサブクラスでは属性を @property宣言して、 @dynamic 扱いにするので(つまり CoreDataフレームワークが動的にメソッドを追加する)、恐らく下記コードでも良いと思う。試したところ設定は問題なし。Undo/Redoは試していない。

 self.createdDate = [NSDate date];

2010年1月26日火曜日

NSWorkspace - recycleURLs:completionHandler:(ゴミ箱に捨てる/復活させる)

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

ファイルをゴミ箱へ捨てるメソッドが Mac OS X v10.6 から NSWorkspace に追加された。

Mac Dev Center: NSWorkspace Class Reference - recycleURLs:completionHandler:

シグネチャはこんな感じ。

- (void)recycleURLs:(NSArray *)URLs completionHandler:(void (^)(NSDictionary *newURLs, NSError *error))handler

ブロックを引数に取るようになっていて、ゴミ箱へファイルを移動した後の処理を書けるようになっている。


ゴミ箱にファイルを捨てる処理は以前紹介したことがある。
Cocoaの日々: 直前のキャプチャ画像をゴミ箱へ捨てる

10.5 まではこんなコードでゴミ箱へファイルを移動していた。

[[NSWorkspace sharedWorkspace]
   performFileOperation:NSWorkspaceRecycleOperation
   source:dir destination:@""
   files:files tag:nil];

難点は一旦ゴミ箱へファイルを移動すると、それを取りやめて元に戻す(復活させる)ことが難しいこと。これはゴミ箱に入るとファイルの URLが変わってしまうため(見た目は変わっていないが、同名のファイルが捨てられても区別できるように内部的には別名で管理されている)。

これが 10.6 から導入された recycleURLs:completionHandler: を使うと、ゴミ箱へ移動すると同時にゴミ箱内でのURLを取得することができる。この URL を使うことで後でゴミ箱から元のファイル探し出して復活させることができる。


動作を見るためにサンプルを作ってみた。

(1)ボタンでデスクトップにplistファイルを作り、(2)ボタンでゴミ箱へ移動する。最後に(3)ボタンでゴミ箱からデスクトップへファイルを復活させる。


ソースコードは次の通り。XcodeのテンプレートでCocoaアプリを作る。

RecycleStudyAppDelegate.h

@interface RecycleStudyAppDelegate : NSObject {
    NSWindow *window;

IBOutlet NSTextField* filenameField;
IBOutlet NSTextField* urlField;

NSURL* originalURL;
NSURL* newURL;
}

@property (assign) IBOutlet NSWindow *window;

-(IBAction)generate:(id)sender;
-(IBAction)moveToTrash:(id)sender;
-(IBAction)undo:(id)sender;

@end


最初にテスト用に作るファイル名のURLを作っておく。
RecycleStudyAppDelegate.m

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application

NSString* path = [NSSearchPathForDirectoriesInDomains(NSDesktopDirectory, NSUserDomainMask, YES) objectAtIndex:0];
path = [path stringByAppendingPathComponent:@"RecycleSample.plist"];

originalURL = [[NSURL alloc] initFileURLWithPath:path];
}


(1)generate : テスト用のファイルをデスクトップに作成する。

-(IBAction)generate:(id)sender
{
NSDictionary* sampleDict = [NSDictionary
dictionaryWithObjectsAndKeys:[NSDate date], @"now", nil];
[sampleDict writeToURL:originalURL atomically:YES];
[filenameField setStringValue:[originalURL description]];
}

中身は現在時刻を詰めてある(特に意味なし)。

(2)move to trash:続いてゴミ箱への移動。

-(IBAction)moveToTrash:(id)sender
{
NSArray* urls = [NSArray arrayWithObject:originalURL];

[[NSWorkspace sharedWorkspace] recycleURLs:urls
completionHandler:^(NSDictionary *newURLs, NSError *error) {
if (error != nil) {
NSLog(@"error: %@", error);
} else {
NSLog(@"newURLs: %@", newURLs);
newURL = [[newURLs objectForKey:originalURL] retain];
[urlField setStringValue:[newURL description]];
}
}];
}

ブロック内で渡される NSDictionary *newURLs にはキーにオリジナルのURL、値にゴミ箱内でのURLが入っている。こんな感じ。

RecycleStudy[18476:80f] newURLs: {
    "file://localhost/Users/hashi/Desktop/RecycleSample.plist" = "file://localhost/Users/hashi/.Trash/RecycleSample.plist";
}


上記ではゴミ箱内でのURLを newURLにとって置いてある。

(3)undo : ゴミ箱からデスクトップへ戻す。


-(IBAction)undo:(id)sender
{
NSError* error = nil;
BOOL result = [[NSFileManager defaultManager]
  moveItemAtURL:newURL toURL:originalURL error:&error];
if (!result) {
NSLog(@"error: %@", error);
}
}


ゴミ箱から復活させる時にはファイルの移動を行えばいい。10.6 からは -[NSFileManager moveItemAtURL:toURL:error:] が用意されたので今回の場合のように URLベースでファイル移動を扱うのには好都合。これを使う。
Mac Dev Center: NSFileManager Class Reference - moveItemAtURL:toURL:error:


さて動かしてみよう。

まずテスト用ファイルを生成する - (1)generate。

デスクトップにファイルが作られる。


次にゴミ箱へ移動する - (2) move to trash

デスクトップからファイルが消えてゴミ箱にファイルが入る。

最後に復活 - (3)undo
デスクトップに再びファイルが出てくる。

ゴミ箱のファイルはなくなる。




なお同名のファイルを複数回ゴミ箱へ捨てると、2個目以降のゴミ箱内のURLには数字がついてユニークになる。



おまけ

ついでに duplicateURLs:completionHandler: も試してみた。
Mac Dev Center: NSWorkspace Class Reference - duplicateURLs:completionHandler:

先程のサンプルプログラムに duplicate ボタンを追加する。

コードはこんな感じ
-(IBAction)duplicate:(id)sender
{
NSArray* urls = [NSArray arrayWithObject:originalURL];

[[NSWorkspace sharedWorkspace] duplicateURLs:urls
  completionHandler:^(NSDictionary *newURLs, NSError *error) {
  if (error != nil) {
  NSLog(@"error: %@", error);
  } else {
  NSLog(@"newURLs: %@", newURLs);
  NSURL* url = [newURLs objectForKey:originalURL];
  [dupField setStringValue:[url description]];
  }
  }];
}

実行してみる。

こんな感じで複製が作られる。複製されたファイル名は「〜のコピー」と日本語にはならず英語("copy")になるようだ。



ソースコード
GitHubからどうぞ
RecycleStudy at master from xcatsan's SampleCode - GitHub

- - - -
recycleURLs:completionHandler: は "Finder-Like Operations" として duplicateURLs:completionHandler: と共に紹介されている。
Mac Dev Center: Low-Level File Management Programming Topics: File Management

2010年1月25日月曜日

NSWorkspace - showSearchResultsForQueryString:

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

Mac OS X v10.6 から追加されたメソッド showSearchResultsForQueryString: を試してみた。
Mac Dev Center: NSWorkspace Class Reference

こんな UI を用意した。ボタンが押されたらテキストフィールド内の文字列を引数として渡す。





コードはこんな感じ。

- (IBAction)search:(id)sender
{
  BOOL result = [[NSWorkspace sharedWorkspace]
    showSearchResultsForQueryString:[textField stringValue]];
  NSLog(@"result=%d", result);
}


試しに "document" と入れてボタンを押すとファインダが開き検索が始まった。


何かに使えるかもしれない。

2010年1月24日日曜日

今週のCocoa情報(1/24) - 今週気になった Cocoaプログラミング情報の紹介

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

Hide instance variable from header file in Objective C
http://stackoverflow.com/questions/2103858/hide-instance-variable-from-header-file-in-objective-c

下記のようなコードの書き方(メンバ変数が定義されていないがプロパティ宣言している)。

@interface MyClass : MySuperClass 
{ 
    //nothing here
}

@property (nonatomic, retain) MyObject anObject;
- (void)someMethod;
64bit アプリと iPhone(実機)のみできるらしい。


cocoa_crypto_hashing
http://projects.stoneship.org/hg/cocoa_crypto_hashing/

NSData, NSString 用 MD5/SHA-1ライブラリ


How to flip coordinates when drawing in context?
http://stackoverflow.com/questions/1145595/how-to-flip-coordinates-when-drawing-in-context

CG系関数を使っている時のフリップ方法。


CGContextTranslateCTM(bitmapContext, 0.0f, originalImage.size.height);
CGContextScaleCTM(bitmapContext, 1.0f, -1.0f);


なお NS系の場合は NSAffintransform が使える。
(参考)NSImage の isFlipped/setFlipped: が Deprecated


NSWorkspace Class Reference
http://developer.apple.com/Mac/library/documentation/Cocoa/Reference/ApplicationKit/Classes/NSWorkspace_Class/Reference/Reference.html

Mac OS X v10.6 でメソッドの追加と廃止(Deprecated)が結構あったようだ。

メソッド追加

デスクトップ関係
- desktopImageOptionsForScreen:
- desktopImageURLForScreen:
- setDesktopImageURL:forScreen:options:error:

Finder連携
- activateFileViewerSelectingURLs:
- fileLabelColors
- fileLabels
- duplicateURLs:completionHandler:
- recycleURLs:completionHandler:
- showSearchResultsForQueryString:

アプリ管理系
- launchApplicationAtURL:options:configuration:error:
- runningApplications
- URLForApplicationToOpenURL:
- URLForApplicationWithBundleIdentifier:

ディスク系
- unmountAndEjectDeviceAtURL:error:

ファインダとの連携が進んだようだ(ラベル操作、スポットライト連携)。デスクトップイメージの取得、設定もできるようになったようだ。その他、気になる機能がいろいろある。後日検証してみたい。


メソッド廃止(Deprecated)
- slideImage:from:to:
- fileSystemChanged
- noteFileSystemChanged
- findApplications
- noteUserDefaultsChanged
- userDefaultsChanged
- checkForRemovableMedia
- mountNewRemovableMedia


通知追加
NSWorkspaceDidHideApplicationNotification
NSWorkspaceDidUnhideApplicationNotification
NSWorkspaceDidActivateApplicationNotification
NSWorkspaceDidDeactivateApplicationNotification
NSWorkspaceDidRenameVolumeNotification
NSWorkspaceDidChangeFileLabelsNotification
NSWorkspaceActiveSpaceDidChangeNotification
NSWorkspaceScreensDidSleepNotification
NSWorkspaceScreensDidWakeNotification

受け取れる通知もいろいろ増えている。