日曜日, 1月 04, 2009

CS 193P/Lecture 1 - Introduction

 スタンフォード大学の、iPhoneアプリケーション開発の講義、さっそくLecture 1をやってみる。

 課題Aに関しては特に言うことはない。開発環境であるXcodeとInterface Builderの極々基本的な操作法に関する課題。文字列と画像を表示させるだけ。

 課題Bは、iPhoneアプリケーションの開発に使われるObjective-Cの基本的な部分の演習になる。
 私のObjective-C経験は、Mac OS X/iPhoneアプリケーション開発で有名なHMDTの木下誠著「たのしいCocoaプログラミング」を買ってさらっと眺めた程度。Objective-CはC言語のスーパセットな言語やけど、私はその基本的なC言語の理解さえ怪しいヘタレプログラマなのです。

 課題B。Xcodeにて、「ファイル」→「新規プロジェクト...」にて、「Command Line Utility」の「Foundation Tool」を選んで、プロジェクト名「WhatATool」で作成。すると、Objective-Cを用いたコマンドラインプログラムのひな形プロジェクトができあがる。
 プログラムのソースファイルWhatATool.mの中身が、
#import <Foundation/Foundation.h>

int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

// insert code here...
NSLog(@"Hello, World!");
[pool drain];
return 0;
}
 こんな感じ。これを
#import <Foundation/Foundation.h>

void PrintPathInfo()
{
}

void PrintProcessInfo()
{
}

void PrintBookmarkInfo()
{
}

void PrintIntrospectionInfo()
{
}

int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

PrintPathInfo();
PrintProcessInfo();
PrintBookmarkInfo();
PrintIntrospectionInfo();

[pool drain];
return 0;
}
な感じに編集して、それぞれの関数の中身を作っていくんが課題B。

課題B-1 Strings as file system paths

 Objective-Cの文字列型NSStringを用いて、文字列チルダ"~"をファイルパスに展開して表示する関数PrintPathInfoを作る。さらに、そのファイルパスに展開された文字列を階層ごとに分割してNSArrayオブジェクトに格納して、表示する。で、こんな感じに。
void PrintPathInfo()
{
NSString *aPath = @"~";
aPath = [aPath stringByExpandingTildeInPath];

NSLog(@"My home directory is at '%@'", aPath);

NSArray *anArray = [aPath pathComponents];
NSEnumerator *enumerator = [anArray objectEnumerator];
id anObject;

while(anObject = [enumerator nextObject]) {
NSLog(anObject);
}
}
 NSStringオブジェクトの簡単な作り方は、Cの文字列リテラルの作り方char *str = "hogehoge";とよく似ている。ただ、文字列"hogehoge"の前に@を付ける。
 チルダ"~"をファイルパスに展開するには、NSStringオブジェクトのインスタンスメソッド- (NSString *)stringByExpandingTildeInPathを使う。オブジェクトに対してメソッドを実行させるには、
[anObject message];
構文を使う。stringByExpandingTildeInPathメソッドの返値はNSStringオブジェクトやけん、元の変数にそのまま代入できる。ファイルパスに展開された文字列が入っとるNSStringオブジェクトを表示するには、
void NSLog (
NSString *format,
...
);
関数を使う。printfライクな形式に対応。NSStringオブジェクトを表示させるときの変換指定子は%@

 次にファイルパスに展開された文字列が入っとるNSStringオブジェクトをパスの階層ごとに分割してNSArrayオブジェクトに格納、表示。NSArrayオブジェクトはObjective-Cの配列オブジェクト。NSStringクラスのメソッド、- (NSArray *)pathComponentsで文字列が階層後に分割されたNSArrayオブジェクトを得る。これを要素毎に表示するわけやけど、課題では列挙を使えと言っとるみたい(?)やけん、NSArrayクラスの- (NSEnumerator *)objectEnumeratorメソッドを使ってみた。

 実行結果がこんな感じ。
$ ./WhatATool 
2009-01-04 23:44:23.037 WhatATool[3595:10b] My home directory is at '/Users/username'
2009-01-04 23:44:23.043 WhatATool[3595:10b] /
2009-01-04 23:44:23.044 WhatATool[3595:10b] Users
2009-01-04 23:44:23.045 WhatATool[3595:10b] username


課題B-2 Finding out a bit about our own process

 課題B-2は実行するプログラム自身のプロセス名とプロセスIDを、NSProcessInfoオブジェクトを用いて取得し、表示する関数PrintProcessInfoを作る。こんな感じ。
void PrintProcessInfo()
{
NSProcessInfo *theProcess = [NSProcessInfo processInfo];

NSLog(@"Process Name: '%@', Process ID: '%d'", [theProcess processName], [theProcess processIdentifier]);
}
 NSProcessInfoクラスのクラスメソッド+ (NSProcessInfo *)processInfoを使うことで、NSProcessInfoオブジェクトを得る。得られたNSProcessInfoオブジェクトに対して、インスタンスメソッドの- (NSString *)processName- (int)processIdentifierを使うことで、プロセス名とプロセスIDが得られる。
 実行結果が
$ ./WhatATool 
2009-01-04 23:53:24.408 WhatATool[3617:10b] Process Name: 'WhatATool', Process ID: '3617'


課題B-3 A little bookmark dictionary

 Objective-Cのハッシュになるんかな、NSDictionaryクラスを使う演習。NSDictionaryに
Key (NSString)Value (NSURL)
Stanford Universityhttp://www.stanford.edu
Applehttp://www.apple.com
CS193Phttp://cs193p.stanford.edu
Stanford on iTunes Uhttp://itunes.stanford.edu
Stanford Mallhttp://stanfordshop.com
というキィと値の対で格納して、それを列挙を用いて表示する。ただし、表示するんは、キィが"Stanford"で始まるもののみ。で、こんな感じ。
void PrintBookmarkInfo()
{
NSArray *keys = [NSArray arrayWithObjects:
@"Stanford University",
@"Apple",
@"CS193P",
@"Stanford on iTunes U",
@"Stanford Mail",
nil];
NSArray *values = [NSArray arrayWithObjects:
[NSURL URLWithString:@"http://www.stanford.edu"],
[NSURL URLWithString:@"http://www.apple.com"],
[NSURL URLWithString:@"http://cs193p.stanford.edu"],
[NSURL URLWithString:@"http://itunes.stanford.edu"],
[NSURL URLWithString:@"http://stanfordshop.com"],
nil];
NSMutableDictionary *aDic = [NSMutableDictionary dictionaryWithObjects:values forKeys:keys];
for(id key in aDic) {
if([key hasPrefix:@"Stanford"]) {
NSLog(@"Key: '%@', URL: '%@'", key, [aDic objectForKey:key]);
}
}
}
 NSArrayオブジェクトを作るにはNSArrayクラスのクラスメソッド+ (id)arrayWithObjects:(id)firstObj, ...を使う。引数の末尾には、終わりを意味する印として、nilを付ける。
 NSURLオブジェクトはNSURLクラスのクラスメソッド+ (id)URLWithString:(NSString *)URLStringを使う。引数として与えたNSStringオブジェクトを用いてNSURLオブジェクトを作る。
 キィとオブジェクトのNSArrayオブジェクトを使って、NSDictionaryクラスのクラスメソッド+ (id)dictionaryWithObjects:(NSArray *)objects forKeys:(NSArray *)keysでNSDictionaryオブジェクトを作る。NSDictionaryオブジェクトはfor(id key in theDictionary) {...}構文でNSDictionaryオブジェクトに格納されているキィを次々に取り出しつつループを回せる。
 NSStringクラスのインスタンスメソッド- (BOOL)hasPrefix:(NSString *)aStringでNSStringオブジェクトが引数で与えた文字列で始まるかどうかを判定できる。実行結果がこんな感じ。
$ ./WhatATool 
2009-01-05 00:13:40.982 WhatATool[3655:10b] Key: 'Stanford on iTunes U', URL: 'http://itunes.stanford.edu'
2009-01-05 00:13:41.037 WhatATool[3655:10b] Key: 'Stanford University', URL: 'http://www.stanford.edu'
2009-01-05 00:13:41.039 WhatATool[3655:10b] Key: 'Stanford Mail', URL: 'http://stanfordshop.com'


課題B-4 Selectors, Classes and Introspection

 オブジェクトが持つ色んな情報を取得する演習。オブジェクトのクラス名、オブジェクトがあるクラスのインスタンスであるか、オブジェクトがあるクラスのインスタンス、もしくはあるクラスを継承するクラスのインスタンスであるか、オブジェクトがあるメソッドを持っているか、といった情報を得る。色んなオブジェクトをNSArrayオブジェクトに格納して、列挙を用いて情報を表示する。こんな感じ。
void PrintIntrospectionInfo()
{
NSMutableArray *anArray = [NSMutableArray arrayWithCapacity:2];
[anArray addObject:[NSString stringWithFormat:@"Hello, World!"]];
[anArray addObject:[NSURL URLWithString:@"http://www.google.com/"]];
[anArray addObject:[NSProcessInfo processInfo]];
[anArray addObject:[NSMutableDictionary dictionaryWithObject:@"hoge" forKey:@"foo"]];

NSEnumerator *enumerator = [anArray objectEnumerator];
id anObject;

while(anObject = [enumerator nextObject]) {
Class aClass = [anObject class];
Class strClass = [NSString class];
NSLog(@"Class name: %s", class_getName(aClass));
NSLog(@"Is Member of NSString: %@", [anObject isMemberOfClass:strClass] ? @"YES" : @"NO");
NSLog(@"Is Kind of NSString: %@", [anObject isKindOfClass:strClass] ? @"YES" : @"NO");
NSLog(@"Responds to lowercaseString: %@", [anObject respondsToSelector:@selector(lowercaseString)] ? @"YES" : @"NO");
if([anObject respondsToSelector:@selector(lowercaseString)]) {
NSLog(@"lowercaseString is: %@", [anObject lowercaseString]);
}
NSLog(@"==================================");
}
}
 あとから変更可能な配列オブジェクトNSMutableArrayを用いて、そのクラスメソッド+ (id)arrayWithCapacity:(NSUInteger)numItemsで、NSMutableArrayオブジェクトを作る。引数numItemsはあくまで初期化時点の要素数であり、あとから拡張できる。インスタンスメソッド- (void)addObject:(id)anObjectで要素を追加。
 列挙を用いたループの中で、クラスの情報が入るデータ型Classの変数を宣言し、すべてのクラスのルートクラスであるNSObjectクラスのインスタンスメソッド- (Class)classの返値を代入する。このClass構造体を引数に取る関数const char * class_getName(Class cls)で、オブジェクトのクラス名を取得できる。
 あるオブジェクトがあるクラスのインスタンスであるか、あるクラスを継承するクラスのインスタンスであるか、あるメソッドを持っているかを判定するには、NSObjectクラスのインスタンスメソッド- (BOOL)isMemberOfClass:(Class)aClass- (BOOL)isKindOfClass:(Class)aClass- (BOOL)respondsToSelector:(SEL)aSelectorを使う。- (BOOL)respondsToSelector:(SEL)aSelectorの引数の型SELはメソッドセレクタ(つまりメソッドの名前?)を表すデータ型である。@selector()コンパイラディレクティヴを用いてメソッドセレクタを作れる。
 実行結果がこんな感じ。
2009-01-05 00:35:31.760 WhatATool[3687:10b] Class name: NSCFString
2009-01-05 00:35:31.763 WhatATool[3687:10b] Is Member of NSString: NO
2009-01-05 00:35:31.764 WhatATool[3687:10b] Is Kind of NSString: YES
2009-01-05 00:35:31.764 WhatATool[3687:10b] Responds to lowercaseString: YES
2009-01-05 00:35:31.765 WhatATool[3687:10b] lowercaseString is: hello, world!
2009-01-05 00:35:31.765 WhatATool[3687:10b] ==================================
2009-01-05 00:35:31.766 WhatATool[3687:10b] Class name: NSURL
2009-01-05 00:35:31.766 WhatATool[3687:10b] Is Member of NSString: NO
2009-01-05 00:35:31.766 WhatATool[3687:10b] Is Kind of NSString: NO
2009-01-05 00:35:31.767 WhatATool[3687:10b] Responds to lowercaseString: NO
2009-01-05 00:35:31.767 WhatATool[3687:10b] ==================================
2009-01-05 00:35:31.768 WhatATool[3687:10b] Class name: NSProcessInfo
2009-01-05 00:35:31.768 WhatATool[3687:10b] Is Member of NSString: NO
2009-01-05 00:35:31.773 WhatATool[3687:10b] Is Kind of NSString: NO
2009-01-05 00:35:31.774 WhatATool[3687:10b] Responds to lowercaseString: NO
2009-01-05 00:35:31.775 WhatATool[3687:10b] ==================================
2009-01-05 00:35:31.776 WhatATool[3687:10b] Class name: NSCFDictionary
2009-01-05 00:35:31.777 WhatATool[3687:10b] Is Member of NSString: NO
2009-01-05 00:35:31.777 WhatATool[3687:10b] Is Kind of NSString: NO
2009-01-05 00:35:31.778 WhatATool[3687:10b] Responds to lowercaseString: NO
2009-01-05 00:35:31.779 WhatATool[3687:10b] ==================================
 ところどころ理解の怪しいところがあるけど、とりあえずLecture 1終了!