ALGOBIT

2010/06/22

シェルからCocoa (3) – メモリ管理

Filed under: 離散的な気まぐれ — タグ: , , , — Kohyama @ 15:28

Cocoaメモリ管理プログラミングガイド
何が行われるか自分なりに理解はできたと思う.
今は分かったけど, どうせすぐ忘れるので, 絵入りの簡潔な説明を作っておきたいけど…

何をするべきかはだいたい以下.
自分が, alloc か new で始まる, または copy を含むメソッド名を持つメソッドによってメモリを確保したオブジェクトには, 不要になった時点で release メッセージを送る.
自分がメモリを確保したオブジェクトでないならば, 理由がなければ何のメッセージを送る必要もない.
自分がメモリを確保したオブジェクトでない場合で, オブジェクトを必要とする範囲を明確にしたいならば, retain メッセージを送って必要であることを通知し, 不要になった時に release メッセージを送る.
確保したオブジェクトへの参照を返すメソッドは, 不要になったことを同じスコープの範囲内で通知することができないので, オブジェクトに autorelease メッセージを送り, 「必要な人が他にいないなら自分はもう要らないです.」と宣言しておく.
main() の最初で NSAutoreleasePool を alloc] init する.
main() の最後でそれを release する.
autorelease されたオブジェクトの管理範囲をもうけたければ,
NSAutoreleasePool の alloc] init と release で挟む.
NSAutoreleasePool はスタックを形成するので, 最後に alloc] init したものから順に, release しなければならない. (release した時点で, 最後の alloc] init 以降に autorelease されたオブジェクトの解放が検討される).
メモリを確保し, 参照を返してくれるメソッドによって, オブジェクトの参照を取得した場合, これが不要になった時点で, 最後に alloc] init した NSAutoreleasePool に release メッセージを送る.

シェルからCocoa (2) のコード直した. main() だけなので実際問題にはならないにしてもね.

#import <Cocoa/Cocoa.h>
#import <QTKit/QTKit.h>

int
main(int argc, char *argv[])
{
	NSAutoreleasePool *ap = [[NSAutoreleasePool alloc] init];
	NSFileManager *fm;
	NSString *path, *newpath, *prefix, *dt, *suffix;
	NSImage *img;
	NSArray *arr;
	NSDictionary *dic;
	QTMovie *mov;
	NSError *err;
	NSDateFormatter *df;

	if (argc < 2)
		exit(1);

	/*
	 * prefix and suffix should be variable
	 * but now hard coded.
	 */
	prefix = @"iph";
	suffix = @"";

	/*
	 * to format an NSDate as an NSString of format "YYYYmmddHHMMSS"
	 */
	df = [[NSDateFormatter alloc]
	    initWithDateFormat:@"%Y%m%d%H%M%S"
   		allowNaturalLanguage:FALSE];
	fm = [[NSFileManager alloc] init];
	while (0 < --argc) {
		path = [NSString stringWithCString: *++argv encoding: NSUTF8StringEncoding];
		dt = nil;
		err = nil;

		if (img = [[NSImage alloc] initWithContentsOfFile:path]) {
			if ((arr = [img representations]) &&
				(dic = [[arr objectAtIndex:0] valueForProperty: @"NSImageEXIFData"])) {
				/* now
				 *   [dic objectForKey:@"DateTimeOriginal"]
				 * returns string like "2010:05:13 12:36:45"
				 * let's retrieve colon(':') and space(' ') from this.
				 */
				dt = [[[dic objectForKey:@"DateTimeOriginal"]
					stringByReplacingOccurrencesOfString:@":" withString:@""]
					stringByReplacingOccurrencesOfString:@" " withString:@""];
			} else {
				NSLog(@"Can't recognize NSImage.DateTimeOriginal field of the file : %@", path);
			}
			[img release];

		} else if (mov = [QTMovie movieWithFile:path error:&err]) {
			if (!err) {
				/* now
				 *   [mov attributeForKey:QTMovieCreationTimeAttribute]
				 * returns the datetime as NSDate
				 * let's format it to "YYYYmmddHHMMSS"
				 */
				dt = [df stringFromDate:
					[mov attributeForKey:QTMovieCreationTimeAttribute]];
			} else {
				NSLog(@"Can't recognize QTMovie.CreationTimeAttributes field of the file : %@",
					path);
			}
		} else {
			NSLog(@"Can't recognize format of the file : %@", path);
		}

		if (dt != nil) {
			newpath = [[[path stringByDeletingLastPathComponent]
				stringByAppendingPathComponent: [[prefix stringByAppendingString: dt]
					stringByAppendingString:suffix]]
				stringByAppendingPathExtension: [path pathExtension]];
			if ([fm fileExistsAtPath:newpath]) {
				NSLog(@"'%@' already exists.", newpath);
			} else {
				err = nil;
				[fm moveItemAtPath:path toPath:newpath error:&err];
				if (err)
					NSLog(@"failed to rename '%@' to '%@'.", path, newpath);
				else
					NSLog(@"'%@' has been renamed as '%@'.", path, newpath);
			}
		}
	}

	[fm release];
	[df release];
	[ap release];
	return 0;
}

2010/06/18

MacOS X のキーボードショートカット

Filed under: 離散的な気まぐれ — タグ: — Kohyama @ 01:44

Windows には, 修飾キーとアルファベットキーを組み合わせてカーソルを動かそうという考えは無い.
従って UNIX 生活では必要の無い, 四つの矢印キー, Home, End, PageUp および PageDown などによるカーソル移動, Shift を押しながらこれらの移動を行うことによる選択範囲の調整, を頻繁に行うことになる.
(といっても常用しているのが HHK なので, Fn キーを押しながら某なのだが,)

iPhone 開発のために Macintosh を購入し, XCode と iPhone SDK のインストールの次にやったことは, XCode のキーボードショートカットを変更し, PageUp キーと PageDown キーをページアップ, ページダウン機能に割り当て直すことだった.

さて, その後 Windows7 / Parallels が実用に耐えそうなので, 基本生活を MacOS X に移行してしばらく.
各アプリケーションで, ではなく, システムデフォルトでテキストを入力する際のキーボードショートカットがある程度定義されている(もちろんアプリケーションあたりで上書き定義できる)ことが分かってきた.
これを変更するにはどうしたら良いか.
Translations/Mac OS X Key Bindings
にすべて書いてあった. 著者さんありがとう.

一番困るのは, システムデフォルトで

Home 文書の先頭にスクロール(カーソルは移動しない)
End 文書の末尾にスクロール(カーソルは移動しない)
PageUp 1画面前へ(カーソルは移動しない)
PageDown  1画面後へ(カーソルは移動しない)

となっていること. これらは Windows 環境では

Home 行頭へカーソル移動
End 行末へカーソル移動
PageUp 1画面前へ(カーソル移動)
PageDown  1画面後へ(カーソル移動)

に割り当てられていることが多く, 非常に頻繁に利用するので, MacOS X で作業中に無意識に押下すると, 予想とまったく異なる機能が呼び出され, プチパニックになる.

そこでこれらのキーは取り敢えず Windows と同じ機能を割り当てておく.
ただし, デフォルト状態の MacOS X を触ったときに困らないように, なるべく別のキーバインドでこれらの機能を呼び出すよう気をつける.
Shift-Home, Shift-End も同様.

システムデフォルトで部分的に Emacs 風のキーバインドが採用されているので, 定義を追加し, 移動系のキーバインドは Emacs 風にしておく.
もちろん vi を使っているとき以外の話だけど.
以下は(自分が)よく使う Emacs バインドのうち, ‘○’ は MacOS X システムデフォルトで採用されているもの, ‘×’ はされていないもの.

○  C-f 1文字進む
C-b 1文字戻る
C-a 行頭へ
C-e 行末へ
C-n 次の行へ
C-p 前の行へ
C-v 1画面分進む(ページダウン)
× M-v 1画面分戻る(ページアップ)
× M-< 文書の先頭へ
× M-> 文書の末尾へ
× C-Space  現在のカーソル位置をマーク
C-k 行末まで削除
C-y 現在のカーソル位置に記憶した内容を書き出す
× C-w 現在のカーソル位置からマーク位置までの内容を削除して記憶
× M-w 現在のカーソル位置からマーク位置までの内容を記憶
× C-x C-x 現在のカーソル位置をマークし, カーソルはマークされていた位置へ移動
× C-x C-m マークまでを選択

上記の採用されていない定義を追加する.
また, M- については Emacs 風に, Meta(Alt, Opt)を押しながらでも, Escape を押してからというのでも機能するように定義を追加する.

というわけで, ~/Library/KeyBindings ディレクトリが無いので作り,
~/Library/KeyBindings/DefaultKeyBinding.dict として以下を作成.

{
    "\UF729" = "moveToBeginningOfLine:";    /* Home */
    "\UF72B" = "moveToEndOfLine:";          /* End */
    "\UF72C" = "pageUp:";                   /* PgUp */
    "\UF72D" = "pageDown:";                 /* PgDn */
    "$\UF729" = "moveToBeginningOfLineAndModifySelection:";  /* Shift-Home */
    "$\UF72B" = "moveToEndOfLineAndModifySelection:";        /* Shift-End */
    "~v" = "pageUp:";                       /* M-v */
    "~<" = "moveToBeginningOfDocument:";    /* M-< */
    "~>" = "moveToEndOfDocument:";          /* M-> */
    "^ " = "setMark:";                      /* C-space */
    "^w" = "deleteToMark:";                 /* C-w */
    "~w" = ("selectToMark:", "copy:");      /* M-w */
    "^x" = {
        "^x"  = "swapWithMark:";            /* C-x C-x */
        "^m"  = "selectToMark:";            /* C-x C-m */
    };
    "\U001B" = {
        "v" = "pageUp:";                    /* M-v */
        "<" = "moveToBeginningOfDocument:"; /* M-< */
        ">" = "moveToEndOfDocument:";       /* M-> */
        "w" = ("selectToMark:", "copy:");   /* M-w */
    };
}

「快適」というよりは「ストレスフルではなくなった」というところ.

2010/06/17

シェルから Cocoa (2)

Filed under: 離散的な気まぐれ — タグ: , , , — Kohyama @ 13:59

UNIX で 100行程度で記述できる処理をするなら, シェルスクリプトを書くか, C のソースを vi で書いて, そのままシェルからコンパイルして実行することが多い. (多かった.)
C で書くことは, UNIX C 開発の経験値を上げるという意義を含んでいる. (含んでいた.)

その経験自体に意味がある人にとっては, 目の前の処理のための最適手法でなくても採用する意味がある. (これはもちろん他の言語で書いたり, わざわざ IDE を起動する場合にも当てはまる. それを経験すること自体に価値があるのだ.)

さて最近主な環境として採用した MacOS X ですが, シェルスクリプトはそのまま使えるので, その範囲を越える場合は Objective-C 言語で Cocoa フレームワークを使います. アップルスクリプトはまた今度.
実はここには, MacOS X 開発の経験値をあげるだけでなく, iPhone, iPad 開発の技能に直結するという隠れた(隠れてないか)意義があります.
ただし, 100行以下の処理なら, XCode 立ち上げるのは面倒なので, vi で編集して, シェルから cc して, 実行.

口上はともかく, 例えば, 目の前の作業.
同じメーカーの機器から取り込んだ写真や動画のファイル名が重複しないように,
また, ファイル名でソートした時に撮影順になるように, “YYYYmmddHHMMSS” の14桁の数字にフォーマットした「撮影日時」を含むファイル名に元のファイルのファイル名を一括変換したい.
ただし, コピーしたり, メールでファイルを送ってもらったりしている間に「ファイルの作成日時」は簡単に更新されてしまいますので, あくまで「撮影日時」です.

Windows 常用中は, VisualBasic .NET で書いたプログラム(数十行)使ってましたが, Exif のみ対応でした.

今回は Cocoa と QTKit で, JPEG に入っている Exif と, QuickTime な動画(mov とか 3gp とか) のヘッダに格納されている撮影日時を認識しましょう.

datenname.m

#import <Cocoa/Cocoa.h>
#import <QTKit/QTKit.h>

int
main(int argc, char *argv[])
{
    NSAutoreleasePool *ap = [[NSAutoreleasePool alloc] init];
    NSFileManager *fm;
    NSString *path, *newpath, *prefix, *dt, *suffix;
    NSImage *img;
    NSArray *arr;
    NSDictionary *dic;
    QTMovie *mov;
    NSError *err;
    NSDateFormatter *df;

    if (argc < 2)
        exit(1);

    /*
     * prefix and suffix should be variable
     * but now hard coded.
     */
    prefix = @"iph";
    suffix = @"";

    /*
     * to format an NSDate as an NSString of format "YYYYmmddHHMMSS"
     */
    df = [[[NSDateFormatter alloc]
        initWithDateFormat:@"%Y%m%d%H%M%S"
           allowNaturalLanguage:FALSE] autorelease];

    fm = [[NSFileManager alloc] init];
    while (0 < --argc) {
        path = [NSString stringWithCString: *++argv encoding: NSUTF8StringEncoding];
        dt = nil;
        err = nil;

        if (img = [[NSImage alloc] initWithContentsOfFile:path]) {
            if ((arr = [img representations]) &&
                (dic = [[arr objectAtIndex:0] valueForProperty: @"NSImageEXIFData"])) {
                /* now
                 *   [dic objectForKey:@"DateTimeOriginal"]
                 * returns string like "2010:05:13 12:36:45"
                 * let's retrieve colon(':') and space(' ') from this.
                 */
                dt = [[[dic objectForKey:@"DateTimeOriginal"]
                    stringByReplacingOccurrencesOfString:@":" withString:@""]
                    stringByReplacingOccurrencesOfString:@" " withString:@""];
            } else {
                NSLog(@"Can't recognize NSImage.DateTimeOriginal field of the file : %@", path);
            }

        } else if (mov = [QTMovie movieWithFile:path error:&err]) {
            if (!err) {
                /* now
                 *   [mov attributeForKey:QTMovieCreationTimeAttribute]
                 * returns the datetime as NSDate
                 * let's format it to "YYYYmmddHHMMSS"
                 */
                dt = [df stringFromDate:
                    [mov attributeForKey:QTMovieCreationTimeAttribute]];
            } else {
                NSLog(@"Can't recognize QTMovie.CreationTimeAttributes field of the file : %@",
                    path);
            }
        } else {
            NSLog(@"Can't recognize format of the file : %@", path);
        }

        if (dt != nil) {
            newpath = [[[path stringByDeletingLastPathComponent]
                stringByAppendingPathComponent: [[prefix stringByAppendingString: dt]
                    stringByAppendingString:suffix]]
                stringByAppendingPathExtension: [path pathExtension]];
            if ([fm fileExistsAtPath:newpath]) {
                NSLog(@"'%@' already exists.", newpath);
            } else {
                err = nil;
                [fm moveItemAtPath:path toPath:newpath error:&err];
                if (err)
                    NSLog(@"failed to rename '%@' to '%@'.", path, newpath);
                else
                    NSLog(@"'%@' has been renamed as '%@'.", path, newpath);
            }
        }
    }

    [ap release];
    return 0;
}

を作っておいて

cc -o datenname datenname.m -framework Cocoa -framework QTKit

でコンパイルし, datenname をパスの通っている場所にコピーしておきます.
使い方は, ファイル名を変換したいところのファイルをワイルドカードなど使って全部引数として渡して実行.

対象とする API の出版元以外が提供する書籍やサイトのサンプルソースは, 関連すると思われるソースコード断片があってもコピーペーストはしません, 出てきたクラスやメソッドをリファレンス で確認し, 一から自分で書くようにします.

経験を貯めるのが目的の一部なので, 自分のソースは再利用しても良いけど, 人のソースは貼付けません. 文法, クラス, メソッドで分からないものがあれば, リファレンスで調べて, できるだけ一から書きます.
部分的にコードをそのまま利用する場合でも, なるべくコピーペーストはせず, 自分の手で入力し, また, 自分が変更してもよい識別子と, API などが提供する識別子を明確に区別するために, 変数名などはなるべく再命名します.

しかし現時点では, Objective-C のメモリ管理モデルがまだ理解できていないので, NSAutoreleasePool まわりは, 参考にしたソース断片を書き写しただけです. これでいいのか分かりません.
次の課題とします.

2010/06/13

シェルから Cocoa

Filed under: 離散的な気まぐれ — タグ: , , , — Kohyama @ 17:36

ターミナルで作業している率が高いひとが, APIの試験とか, 自分向けツールとかで, 小さな Cocoa アプリを書く.
そんな時は vi でソース編集して cc で.

% cat hoge.m
#import <Foundation/Foundation.h>
int main() {
    NSLog(@"Hello Cocoa");
}
% cc -o hoge hoge.m -framework Foundation
% ./hoge
2010-06-13 17:16:42.470 hoge[88380:903] Hello Cocoa

2010/04/03

iPhone 開発始めの一歩

Filed under: 離散的な気まぐれ — タグ: , , — Kohyama @ 00:54


Xcode と iPhone SDK 入れました.
コードエディタはなんか Emacs っぽいバインドなんですね.
Emacs キーバインドも, Mac のコマンドキー主体のキーバインドも, 手が覚えているので, 頭のスイッチをいれればそれぞれはすんなりいきますが, 二つを頻繁に切り替えるには, ちょっと頭が固くなってる.
シミュレータで, ボタンを押すとテキストフィールドからテキストフィールドへ文字列をコピーする動作を実装してみました. 新しい開発or動作環境でまずやってみるチュートリアルです.

今日はここで終わり.

実機で動かないと意味ない訳ですが, その辺は手続き的な敷居があるようです.
そのあたりもご助言くださる方がおります.
ご相談に乗ってくださる方はもちろん, 開発の初期段階を解説してくださっているブログ, 無料記事など. 心から感謝します.

2010/03/28

MacとiPhone

Filed under: 離散的な気まぐれ — タグ: , , — Kohyama @ 01:18


何周か分からないほど周回遅れで iPhone 開発に参画すべく, 社で MacBook Pro と iPhone を調達しました.
これからセットアップです.
先輩諸氏には折に触れご指導を仰ぎたいと思いますので, 何卒よろしくお願いいたします.

Copyright © 2010 Yoshinori Kohyama All Rights Reserved.