2012年12月23日日曜日

sandbox化したアプリからの他のアプリの起動に関する注意点

OS X 10.5以降ファイルの隔離機能が導入され、インターネットからダウンロードした実行ファイルを初めて開くときに、そのファイルを開くかどうかを確認するダイアログが表示されるようになったのはご存知の通りだと思います。アプリケーションをsandbox化した場合、この機能が問題を引き起こします。インターネットからダウンロードされて一度も開かれていない実行ファイルをsandbox化したアプリケーションから起動しようとしても起動できません。回避策はありませんので、ユーザーにFinderで一度その実行ファイルを開いてもらうよう促すしかありません。(OS X 10.7.5/10.8.2で確認)
ファイルの隔離機能は、アプリがダウンロードしたファイルの拡張属性にcom.apple.quarantineプロパティを付加することによって実現されています。この拡張属性を持つファイルを初めてFinderで開こうとすると、ファイルを開くかどうかを確認するダイアログが表示されます。一度ファイルを開くとこのプロパティが削除され、以後、ダイアログは表示されなくなる仕組みです。ターミナルで"xattr ファイル名"コマンドを実行するとこの挙動が確認できるはずです。
通常、拡張属性はremovexattrシステムコールで削除することが可能ですが、sandbox化したアプリケーションからは削除できません。また、Launch ServiceのLSSetItemAttributesコマンドを使っても削除できませんので、回避策がありません。

フレームワークのUTIに関する注意点

Mac OS X 10.7.5で確認したところ、フレームワークのディレクトリ(.framework)はFinder上では通常のフォルダのように内容を見ることができますが、UTI(Universal Type Identifier)上ではpublic.folderの子孫ではなく、public.directory/com.apple.bundle/com.apple.frameworkという関係になっています。ですので、通常のフォルダのように見えるディレクトリなのかどうかを判定するためには
  UTTypeComformTo(uti, kUTTypeFolder) || UTTypeComformTo(uti, kUTTypeFramework)
とする必要があります。なお、ボリュームのUTIであるpublic.volumeはpublic.folderの子孫になっています。

2012年8月23日木曜日

Xcode 4.4.1は10.6SDK非サポート

9/1訂正
下記の方法だとリンク時にエラーが発生してしまいます。正しくは、Build SettingsでBase SDKをOS X 10.7、OS X Deployment TargetをOS X 10.6にすることでMac OS X 10.6でも動作するアプリを作ることができます。

8/7にXcode 4.4.1が公開されましたが、このバージョンから10.6SDKが非サポートとなってしまいました。Mac OS X 10.6でも動作するアプリを開発したい場合は、古いバージョンのXcodeから10.6SDKをXcode 4.4.1のパッケージ内のXcode.app/Contents/Developer/Platforms/MacOSX.platformにコピーすれば開発できるようになります。古いバージョンのXcodeはデベロッパー会員であればhttps://developer.apple.com/downloads/index.actionからダウンロードできます。Xcode 3.xであれば/Developer/SDKs/の下に、Xcode 4.xであればXcode.app/Contents/Developer/Platforms/MacOSX.platformの下に10.6SDKのフォルダがあります。

2012年2月25日土曜日

LionのIKImageBrowserのバグ

Lion(10.7.3で確認)ではIKImageBrowserのcellSizeを256*96のように横長のサイズに設定すると、右のスクリーンショットのように、上下のセルが重なって表示されてしまう問題があります。これはimageFrameの高さが実際のセルの高さより高く計算されてしまうのが原因のようですので、下記のようにNSImageBrowserCellのimageFrameをオーバーライドして、強制的にimageFrameの高さを実際のセルサイズ内に収めると問題を回避できます。



- (NSRect)imageFrame
{
NSRect imageFrame;
NSRect frame;
CGFloat maxHeight;
CGFloat ratio;
// Work around code for Mac OS X 10.7.x Lion's bug.
frame = [super frame];
imageFrame = [super imageFrame];
maxHeight = NSHeight(frame) - 20;
if(NSWidth(imageFrame) > maxHeight) {
ratio = maxHeight / NSWidth(imageFrame);
imageFrame.size = NSMakeSize(maxHeight, NSHeight(imageFrame) * ratio);
imageFrame.origin = NSMakePoint(NSMidX(frame) - NSWidth(imageFrame) / 2
NSMidY(frame) - NSHeight(imageFrame) / 2 + 10);
}
return imageFrame;
}


2012年2月20日月曜日

非同期メソッドを同期実行する方法

completionHandlerを引数に取るCocoaの非同期メソッドを同期実行したい場合、あまりスマートな方法ではないですが、下記の例のようにすると同期実行できます。completionHandlerはメソッドを呼び出したスレッドと同じスレッドで実行されますので、run loopをまわさないでNSLockなどで同期しようとするとデッドロックになることに注意。

__block BOOL finished = NO;

[[NSWorkspace sharedWorkspace] recycleURLs:trashURLs
 completionHandler:^(NSDictionary *newURLs, NSError *error) {
 finished = YES;
 }];

while(finished == NO) {
 [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
}

Cocoaアプリでコンピュータ名を取得する方法

CoreServices.frameworkの関数CSCopyMachineName()で取得できます。返される文字列はretainされているので、使い終わったらreleaseしましょう。

Cocoaでファイル名変更

ファイル名を変更するAPIとしてNSFileManagerにmoveItemAtPath:toPath:error:が用意されていますが、元のファイル名と大文字小文字が違うだけのファイル名には変更できないようです(Mac OS X 10.6.6で確認)。例えば、TEST.txtをtest.txtに変更する場合などです。これを回避するためには、下記のようにrenameシステムコールを使うと良さそうです。


manager = [NSFileManager defaultManager];
if([manager fileExistsAtPath:newPath] == NO || 
    [path caseInsensitiveCompare:newPath] == NSOrderedSame) {
  rename([path fileSystemRepresentation], [newPath fileSystemRepresentation]);
}


renameシステムコールは第2引数に指定したファイルが既に存在する場合はそれを消してしまうので、あらかじめファイル名変更後のファイルが存在するかどうかを確認して、ファイルが存在しないか、元のファイル名と新しいファイル名が大文字小文字が違うだけの場合のみ名前を変更するようにします。

ファイルサイズを取得する方法

Cocoaでファイルサイズを取得する方法はいくつかあります。
1つはNSFileManagerのattributesOfItemAtPath:error:で返されるNSDictionaryのNSFileSizeキーの値を取得する方法ですが、この方法ではファイルの論理サイズは取得できますが、物理サイズ(アロケートサイズ)は取得できません。
もう一つの方法はMac OS X 10.6から追加されたNSURLのgetResourceValue:forKey:error:を使う方法です。このメソッドで返される辞書のNSURLFileSizeKey、NSURLFileAllocatedSizeKeyの値を取得する事で論理サイズ、物理サイズが取得できます。ただ、この方法でもデータフォークのサイズしか取得できないようです。
リソースフォークも含めたサイズを取得するためにはCarbon File ManagerのFSGetCatalogInfoかFSIterateForksを使うしかないようです。CarbonのFile Managerを使うためにはパスを表すNSStringからFSRefを取得する必要がありますが、これは下記のようにすれば取得できます。

 FSRef   fileRef;
 OSStatus status;

 status = FSPathMakeRef((const UInt8*)[path fileSystemRepresentation], &fileRef, NULL);

IKImageViewのメモリリーク?

IKImageViewにsetImage:imageProperties:で画像をセットすると、IKImageViewのインスタンスを解放してもセットした画像が解放されない問題があるようです(Mac OS X 10.6.8で確認)。IKImageViewのインスタンスを解放する前に
 [imageView setImage:nil imageProperties:nil];

を実行すれば問題を回避できるようです。

IKImageBrowserCellのtitleFrameメソッドのクセ

IKImageBrowserViewに表示されるアイテムのタイトル領域を取得するために、IKImageBrowserCellクラスにtitleFrameメソッドが用意されていますが、スクロールビューの表示領域外にあるなどの理由で一度も描画されたことのないアイテムに対してこのメソッドを呼び出すと、正しいタイトル領域が返されないようです。