2011年9月28日水曜日

Objective-Cのオブジェクト初期化エラーに対処する方法

当たり前だけどオブジェクトの初期化に失敗することもあるわけで、その場合はリソースの解放などエラー処理が必要になる。以前書いたクラスを見直していたら、イニシャライザのエラー処理で怪しいコードがあったので正しい方法を調べてみた。

Appleの「The Objective-C Programming Language*」を見てみると答えがそのまま書かれていて、selfに対してreleaseを呼んでからnilを返すとなっている。releaseによってdeallocが呼ばれるので、self自体とインスタンス変数に割り当てたオブジェクトが解放されることになる。

* Allocating and Initializing Objects > Implementing an Initializer > Handling Initialization Failure

画像データをNSDataで受け取りUIImageとして保持するクラスがあったとすると、次のような感じになる。

- (id)initWithData:(NSData*)data
{
    self = [super init];
    // superのイニシャライザがnilを返してもreleaseは必要ない
    // 既にsuperで行われているため
    if (self) {
        image = [[UIImage alloc] initWithData:data];
        if (!image) {
            // imageの解放は必要ないけどself自体の解放が必要
            [self release];
            return nil;
        }
    }
    return self;
}

- (void)dealloc
{
    // 初期化に失敗していてもimageはnilなので問題ない
    [image release];
    [super dealloc];
}

deallocは初期化に失敗した状態で呼ばれることもあるため、そのような場合でも正しく動くようにしなければいけない。とは言え、上記のコードのように単純にインスタンス変数をreleaseするだけなら問題はない。

  1. allocはメモリを割り当てた後、すべてのインスタンス変数を0にセットする(0 == nil
  2. インスタンス変数に割り当てるオブジェクトの初期化に失敗しても、そのオブジェクトのイニシャライザはnilを返すため、インスタンス変数はnilにセットされる
  3. nilに対するメソッドの呼び出しは何も起きないことが保証されている

したがって、インスタンス変数はオブジェクトが割り当てられているか、nilがセットされているかのどちらかなので、deallocは正常に実行される。

ちなみに、free(NULL)delete 0も何も起きないことが保証されている。ただし、CFRelease

If cf is NULL, this will cause a runtime error and your application will crash.

とのことなので、NULLチェックが必要(CFType Reference)。

詳解 Objective-C 2.0 改訂版
詳解 Objective-C 2.0 改訂版
  • 発売元: ソフトバンククリエイティブ
  • 価格: ¥ 3,990
  • 発売日: 2010/12/17

2011年8月21日日曜日

UINavigationBarやUIToolbarの高さを変更することは禁止されている

iOS Human Interface Guidelinesから引用。

iOS UI Element Usage Guidelines > Bars > Navigation Bar

On iPhone, take into account the automatic change in navigation bar height that occurs on device rotation. In particular, make sure your custom navigation bar icons fit well in the thinner bar that appears in landscape orientation. Don’t specify the height of a navigation bar programmatically.

iOS UI Element Usage Guidelines > Bars > Toolbar

On iPhone, take into account the automatic change in toolbar height that occurs on device rotation. In particular, make sure your custom toolbar icons fit well in the thinner bar that appears in landscape orientation. Don’t specify the height of a toolbar programmatically.

ということで、UINavigationBarUIToolbarの高さを変更してはいけない。ただし、UINavigationBarの上部にテキストを表示するだけなら、promptプロパティが使用できる。

2011年8月17日水曜日

HomebrewでCocoa Emacsをインストールした

少し前にMacBook Pro 13インチ (Early 2011)を購入して、いろいろと設定するついでに、これまで使ってきたCarbon EmacsからCocoa Emacs (Emacs 23)へ移行することにした。最初はバイナリで簡単にインストールできると思っていたけど、日本語入力が完全ではないようでパッチを当てたほうがいいことがわかった。パッケージ管理はHomebrewで行っているのだけど、Formulaを書き換えることでパッチの追加も簡単にできるし、EmacsもHomebrewでインストールすることにした。

パッチについて

パッチはいろいろ公開されているけど、日本語入力を快適にするためのインラインパッチ(IMEパッチ)は必須のようだ。また、インラインパッチのバグを修正するパッチが「Cocoa Emacs が落ちる」で公開されているので使わせて頂く。

さらに、「typester/emacs - GitHub」で公開されているfeature-fullscreen.patchも定番らしい。これはM-x ns-toggle-fullscreenでEmacsがフルスクリーンになる機能を追加する。同じ場所にfix-shiftmodifier-with-ime.patchも公開されていて、こちらは日本語IMEでShiftが使えない問題を修正する。これはインラインパッチが提供する機能で回避した方がいいらしいので使わないことにする。

Homebrewでインストール

HomebrewでFormulaを編集するには、

$ brew edit emacs

のようにする。Formulaを開いた状態で$EDITORが立ち上がるので書き換えて保存すればいい。

実際にFormulaを開いて気付いたのだけど、すでにfullscreen patchは含まれている(バージョンは23.3a)。とういことで、必要なパッチはインラインパッチとそのバグを修正するパッチ、あと宣言と定義で引数の型が違ってコンパイルエラーになってしまう部分があったので、それを修正するパッチも追加した(Xcodeのバージョンは3.2.6)。

最終的にpatchesメソッドは次のようになった。

(2011/11/12 追記)最新のFormulaに合わせて修正。Fix for Shift key...はインラインパッチとかぶるのでコメントアウトした。

def patches
  p1 = []
  p0 = []

  # Fix for building with Xcode 4; harmless on Xcode 3.x.
  unless ARGV.build_head?
    p1 << "http://repo.or.cz/w/emacs.git/commitdiff_plain/c8bba48c5889c4773c62a10f7c3d4383881f11$
    # Fix for address randomization on Darwin. Based on:
    #   http://repo.or.cz/w/emacs.git/patch/f2cea124dffac9ca4b8ce1dbb9b746f8e81109a3
    p1 << "https://raw.github.com/gist/1098107"
    # Fix for the titlebar issue on Mac OS X 10.7
    p1 << "https://raw.github.com/gist/1102744"
    # Fix for Shift key for IME users
    #p1 << "https://raw.github.com/gist/1212776"
  end

  if ARGV.include? "--cocoa"
    # Fullscreen patch, works against 23.3 and HEAD.
    p1 << "https://raw.github.com/gist/1012927"

    p0 << "http://sourceforge.jp/frs/redir.php?m=jaist&f=%2Fmacemacsjp%2F47986%2Finline_patch-23.2-beta3.tar.gz"
    p0 << DATA
  end

  return { :p1 => p1, :p0 => p0 }
end

Hashを返すと:p1の値はpatch -p1に、:p0の値はpatch -p0に渡される。

さらに、ファイルの一番最後に以下を追加する。

__END__
diff -U0 src/macim.m.orig src/macim.m
--- src/macim.m.orig
+++ src/macim.m
@@ -42 +42 @@
-int mac_pass_key_to_system (int code, UInt32 modifiers);
+int mac_pass_key_to_system (int code, unsigned modifiers);
diff -U0 src/nsterm.m.orig src/nsterm.m
--- src/nsterm.m.orig
+++ src/nsterm.m
@@ -3959 +3959 @@
-  if (mac_store_change_input_method_event())
+  if (emacs_event && mac_store_change_input_method_event())

__END__以降はスクリプトではなく、DATAという定数でファイルオブジェクトとして扱うことができる。

Formulaの編集が終わったら次のようにしてインストールする。できたEmacs.appは/Applicationsに移動してしまう。

brew install emacs --cocoa
mv /usr/local/Cellar/emacs/23.3a/Emacs.app /Applications

初期設定

最低限必要な設定は以下の通りで、2行目が日本語IMEのShiftの問題を回避する。

(setq default-input-method "MacOSX")
(mac-add-key-passed-to-system 'shift)

デフォルトではOptionキーがMetaになっている。CommandキーをMetaとして使うためにOptionキーと入れ替える。

(setq ns-command-modifier (quote meta))
(setq ns-alternate-modifier (quote super))

ドラッグ&ドロップでファイルを開く。2行目は新しいウィンドウで開くのを防ぐため。

(define-key global-map [ns-drag-file] 'ns-find-file)
(setq ns-pop-up-frames nil)

円記号の代わりにバックスラッシュを入力する。ただし、delete-horizontal-spaceM-¥ is undefinedと表示されてしまう。

(mac-translate-from-yen-to-backslash)

フォント設定は

が参考になる。