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/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

Copyright © 2010 Yoshinori Kohyama All Rights Reserved.