☆Cocoa Foundationクラス(等)勉強室2
ファイル操作;NSFileManager/NSFileHandle
iOSは見かけ上「ファイルという概念が非常に薄い」。 少なくとも、WindowsやMacの様に、ユーザーがファイルを直接操作する環境が存在しない。 基本的には、ファイルはアプリケーションごとに保持しており、 データファイルの共有はいくつかのクラスを用いて可能になっているが、 たとえばフォントなど、リソースの共有とか言う概念はない。

なぜ「ファイルという概念を隠蔽する」のか。 ファイルというのは、コンピューターにとっては基本中の基本の概念ではあるが、 結構難しいものの考え方である。それを「携帯電話」程度の物で意識させることは必要ない、ということであろう。 家電と一緒なのだ。最近はTVでインターネットアクセスが出来る様になっているが、 そこにファイルの概念は見えてこない。レコーダーだって、内部ではファイルで画像を記録しているはずだが、 それを見せない。そういうことだ。
PCのファイルシステムに慣れている人が、iOSデバイスを触るとまず面食らうのがここだ(と思う)が、 このデバイスの向かっている先を考えれば、これは仕方ないことである。

ではあっても内部的にはファイルの概念はあるようなので、そのクラスを見ていく。
なお、以降「フォルダ」と「ディレクトリ」は同じ意味である。 Mac上のフォルダはそれを1つのファイルとして認識させることも可能で、真の意味で「フォルダ」であったが、 iOSのそれはWindowsと同じでディレクトリ構造そのものだけである。

NSFileManager
メソッド名動作
+(NSFileManager *)defaultManager デフォルトのファイルマネージャーを返す
-(BOOL)fileExistsAtPath:(NSString *)pathファイルが実際に存在しているか調べる
-(BOOL)createFileAtPath:(NSString *)path contents:(NSData *)contents attributes:(NSDictionary *)attributes 内容データで名前と属性を指定してファイルを作る
-(NSData *)contentsAtPath:(NSString *)pathパスで指定されるファイルからNSDataを作る
-(BOOL)changeCurrentDirectoryPath:(NSString *)path現在のディレクトリパスを変更
-(NSString *)currentDirectoryPath現在のディレクトリパスを返す
-(BOOL)createDirectoryAtPath:(NSString *)path withIntermediateDirectories:(BOOL)createIntermediates attributes:(NSDictionary *)attributes error:(NSError **)error 属性を指定して空のディレクトリを作る
-(BOOL)copyItemAtPath:(NSString *)srcPath toPath:(NSString *)dstPath error:(NSError **)error srcPath から desPath にディレクトリまたはファイルをコピーする
-(BOOL)moveItemAtPath:(NSString *)srcPath toPath:(NSString *)dstPath error:(NSError **)error srcPath から desPath にディレクトリまたはファイルを移動する
-(BOOL)removeItemAtPath:(NSString *)path error:(NSError **)error 指定したパスのファイル/ディレクトリを削除
-(BOOL)contentsEqualAtPath:(NSString *)path1 andPath:(NSString *)path2 ファイルやディレクトリの中身を比較
-(BOOL)fileExistsAtPath:(NSString *)pathファイルが実際に存在しているか、またディレクトリかどうかを返す
-(BOOL)fileExistsAtPath:(NSString *)path isDirectory:(BOOL *)isDirectory 指定パスの中にファイルが実際に存在しているか、またディレクトリかどうかを返す
-(BOOL)isReadableFileAtPath:(NSString *)pathパスのファイルは読み込み可能か調べる
-(BOOL)isWritableFileAtPath:(NSString *)pathパスのファイルは書き込み可能か調べる
-(NSDirectoryEnumerator *)enumeratorAtPath:(NSString *)path指定したパスのディレクトリの内容をディレクトリ列挙子オブジェクトで返す
-(NSArray *)subpathsAtPath:(NSString *)path全てのファイルやディレクトリ(サブディレクトリも含めて)のパスの配列を返す
ファイル属性キー
以下のキーは、NSDictionary オブジェクトで ファイル属性値にアクセスするためのキーである。 使うのは以下のメソッド。
createDirectoryAtPath:withIntermediateDirectories:attributes:error:
createFileAtPath:contents:attributes:
(NSString定数値)
NSFileTypeファイル属性値(後述)
NSFileSizeファイルサイズ(バイト);リソースフォークのサイズは含まない
NSFileModificationDate最終変更日
NSFileReferenceCount参照回数
NSFileOwnerAccountNameファイル保有者の名前
NSFileSystemFileNumberファイル番号
NSFileExtensionHiddenファイルの拡張子を隠すかどうか
NSFileImmutableファイルが可変かどうか
NSFileAppendOnlyファイルが読み込み専用かどうか
NSFileCreationDateファイル作成日
NSFileBusyファイルがBUSYかどうか

たくさんのメソッドが存在するが、その全部を覚える必要はない。 Cの標準関数にも多数のファイルアクセス関数があるが、使うのはごく一部だけで十分なのと同じである。 実際のファイルアクセスに必要そうな部分のみを使ってみる。 なお、上記を見れば解るが、実際にファイルに読み書きするメソッドはこの中には含まれていない。 それは、NSArray/NSMutableArrayやNSDictionaryなど、データを保持する側のクラスが持っているからである。

まずは基本的なファイルアクセス方法。iOSレベルではこれだけで十分かもしれない。
// ファイルポインターを取得する
NSFileManager *fileManager = [NSFileManager defaultManager];

// ディレクトリ検索リストを作成する
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);

// paths[0]を取り出す;そこにディレクトリ名が入っているらしい
NSString *documentsDirectory = (([paths count] > 0)? // 見つかったら
                  [paths objectAtIndex:0]   // 最初のもの
                : NSTemporaryDirectory() ); // なければ一時ディレクトリ

// ファイルパスの末尾にファイル名(ここでは dictionary.plist )を追加
NSString *dataPath = [documentsDirectory stringByAppendingPathComponent:@"dictionary.plist"];

// ファイルの存在を確認
if ([fileManager fileExistsAtPath:dataPath]) {
        // 辞書をファイル内容から生成する
        dictionary = [[NSDictionary alloc] initWithContentsOfFile:dataPath];
}
ここでは珍しくクラスではない関数を使っている。
NSArray *NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory directory
                                           , NSSearchPathDomainMask domainMask
                                           , BOOL expandTilde)
動作
ディレクトリ検索パスのリストをつくる

引数
directoryフォルダを指定 NSPathUtilities.h
NSDocumentDirectory ドキュメント
NSApplicationDirectory アプリケーション
NSDesktopDirectory デスクトップ
NSLibraryDirectory ライブラリ
NSUserDirectory ユーザー
NSDeveloperDirectory デベロッパー
NSApplicationSupportDirectory アプリケーションサポート
NSDeveloperApplicationDirectory デベロッパー/アプリケーション
NSAllApplicationsDirectory すべてのアプリケーションのディレクトリ
NSAllLibrariesDirectory すべてのライブラリのフォルダディレクトリ
domainMaskどこのディレクトリを探すか
NSUserDomainMask ユーザホーム
NSSystemDomainMask システム
NSLocalDomainMask ローカル
NSNetworkDomainMask  ネットワーク
NSAllDomainsMask 全て
expandTilde~(チルダ)を展開するかどうかYES=展開する/NO=しない
チルダ付きというのはユーザーディレクトリであるということ、らしい
返り値
        配列の中にパスが入る
        複数の場所を探す様に指定した場合は、複数の値が返ってくるのでが配列になっている
である。よくわからん部分もあるが、「この例の通りに使う」とだけ覚えておけばいいのだろう。

この例では「辞書」を読み込んでいるが、配列でも基本的に全く同じである。 これを書くために色々と調べてみたが、どうもCocoa Touchを使う上ではファイルのオープンやクローズはする必要がなさそうである。 Cでファイル操作を多数書いてきた身にとっては違和感があるが、それはそれで楽なのかもしれない。 しかし、OSレベルでオープン・クローズがないはずはないので、イニシャライザとクラスの終了処理で行われているのだろう。

もう少しCに近いファイルアクセスを実現する場合にはNSFileHandleを使う。 バックグラウンド処理も標準で用意されている。
NSFileHandle
メソッド名動作Cの相当関数など
+(id)fileHandleForReadingAtPath:(NSString *)path読み込みファイル/デバイスハンドルを作るfopen
+(id)fileHandleForReadingAtURL:(NSURL *)path error:(NSError **)error読み込みファイル/デバイス/URLハンドルを作るfopen("rb")
+(id)fileHandleForUpdatingAtPath:(NSString *)path読み書きファイル/デバイスハンドルを作るfopen
+(id)fileHandleForUpdatingAtURL:(NSURL *)path error:(NSError **)error読み書きファイル/デバイス/URLハンドルを作るfopen("rw")
+(id)fileHandleForWritingAtPath:(NSString *)path書き込みファイル/デバイスハンドルを作るcreat
+(id)fileHandleForWriteingAtURL:(NSURL *)path error:(NSError **)error書き込みファイル/デバイス/URLハンドルを作るfopen("w")
+(id)fileHandleWithStandardError標準エラー出力のファイルハンドルを作るstderr
+(id)fileHandleWithStandardInput標準入力のファイルハンドルを作るstdin
+(id)fileHandleWithStandardOutput標準出力のファイルハンドルを作るstdout
+(id)fileHandleWithNullDeviceNULLデバイスのファイルハンドルを作る 
-(id)initWithFileDescriptor:(int)fileDescriptorファイルデスクリプタで初期化して、ファイルハンドルを返すopen
-(NSData *)availableDataファイルポインタからファイルの終わりまで読んだNSDataを返す
-(void)closeFileファイルを閉じるfclose
-(NSData *)readDataOfLength:(NSUInteger)length指定した長さ分を読んでNSDataを返すfread
-(NSData *)readDataToEndOfFileファイルの終わりまでを読んでNSDataを返す 
-(void)writeData:(NSData *)dataファイルハンドルにNSDataを書くfwrite
-(void)readInBackgroundAndNotifyForModes:(NSArray *)modesバックグラウンドで読んで、通知する 
-(void)readInBackgroundAndNotifyバックグラウンドでファイルを読んで、読み終わったら通知する 
-(void)readInBackgroundAndNotifyForModes:(NSArray *)modes:バックグラウンドでファイルの最後まで読んで、通知する 
-(void)readInBackgroundAndNotifバックグラウンドでファイルの最後まで読んで、通知する 
-(unsigned long long)offsetInFileファイルポインタの位置を返すftell
-(unsigned long long)seekToEndOfFileファイルの最後を探するfeof
-(void)seekToFileOffset:(unsigned long long)offset指定した位置にファイル読み出しポインタを移動fseek
-(void)synchronizeFileメモリ上ディスクキャッシュ残るデータをディスクに吐き出すfflush
-(void)truncateFileAtOffset:(unsigned long long)offset指定するオフセットでファイルを切り詰めたり広げたりする 
// パスをフルパスにする
NSString* outputFilePath = [outputFilePath stringByExpandingTildeInPath];

// 出力するファイルを生成(書き込み側が新規の場合は必須)
[[NSFileManager defaultManager] createFileAtPath:outputFilePath contents:nil attributes:nil];

// ファイルポインターを取得
NSFileHandle* ifp = [NSFileHandle fileHandleForReadingAtPath:inputFilePath];
NSFileHandle* ofp = [NSFileHandle fileHandleForWritingAtPath:ouputFilePath];
// ファイルがない場合はnilが返ってくる
// 書き込み側が新規の場合も、先にファイルを作っておかないと「存在しない」になるので注意

// 出力ループ
@try {
    NSData* data
    while (data = [ifp readDataOfLength:1]) {
            [ofp writeData:data];
    }
} @finally {
    [ifp closeFile];
    [ofp closeFile];
}
ファイルの新規作成の場合、ファイル作成(createFileAtPath:)とファイルポインターの取得が別になるのが注意点。 既存ファイルの場合はファイル作成の処理は省ける。
ここではバイナリデータの格納できるNSDataを使って1バイト毎Read→Writeしている。 やはりここでもファイルオープン処理はない。クローズは一応書かれているが、 エラー時のみしか通らない。正常処理に関してはそれらは不要のようだ。

Objective-CはCの完全互換だから、Cの標準関数でのファイルアクセスも出来る。 私はCに慣れているのでほとんどがCの関数で書いてしまう。 特に問題はないようである。


URL操作;NSURL/NSURLConnection/NSURLRequest/NSURLResponse
NSURLはURLを操作するクラスである。 ファイル(パス)名はNSStringであるが、URLはNSStringではなく専用のクラスが与えられている。 NSStringの子クラスであると思われるが、URLを分解するメソッド等が追加される。 はっきり言って、URLの構成自体よく知らないが、少なくとも「URLはNSStringで処理するのではない」ということだけは理解しておかなければならない。

まずは NSURL のURL分解系。URLに小細工しておけば、どこから飛んできたかなどを知ることができるので、それを取得するときなどに使える。
メソッド名動作
+(id)URLWithString:(NSString *)URLString文字列でNSURLを作る
-(NSString *)absoluteStringURLをNSStringで取得する
-(NSURL *)absoluteURLURLをNSURLで取得する
-(NSString *)schemeレシーバーのスキームを取得するhttp,ftp,mailto,file等
-(NSString *)hostホストを取得する
-(NSNumber *)portポート番号を取得する指定なしの場合は0
-(NSString *)fragmentアンカーを取得する
-(NSString *)pathパスを取得する
-(NSString *)queryクエリーを取得する
-(NSString *)parameterStringパラメータ文字列を取得する
-(NSString *)passwordパスワードを取得する
-(NSString *)userユーザー名を取得する
-(NSURL *)baseURLレシーバーのベースURLを取得する
-(NSString *)resourceSpecifierリソース指示子を取得する
-(NSString *)relativePath関連パスを取得する
-(NSString *)relativeString関連パスを取得する
NSURL *url_data = [NSURL URLWithString:@"https://www.test.jp:8080/cgi-bin/test.cgi?TEST=1&TEST2#TEST3"];
absoluteString(NSString*)@"https://www.test.jp:8080/cgi-bin/test.cgi?TEST=1&TEST2#TEST3"
absoluteURL(NSURL*)@"https://www.test.jp:8080/cgi-bin/test.cgi?TEST=1&TEST2#TEST3"
resourceSpecifierhttps://www.test.jp:8080/cgi-bin/test.cgi?TEST=1&TEST2#TEST3
schemehttps
hostwww.test.jp
port8080
fragmentTEST3
path/cgi-bin/test.cgi
relativePath/cgi-bin/test.cgi
queryTEST=1&TEST2
parameterStringnil
passwordnil
usernil
baseURL
relativeString

次に NSURL のURL操作系。
メソッド名動作
-(BOOL)isFileURLファイルのURLか調べる
+(id)URLWithString:(NSString *)URLString relativeToURL:(NSURL *)baseURLベースURLと文字でNSURLを作って初期化して返す
URLの書式はHTML中のmailto:の書式である(RFC822ではない)
+(id)fileURLWithPath:(NSString *)pathパスからURLを作る
-(id)initFileURLWithPath:(NSString *)pathファイルパスからNSURLを初期化して返す
-(id)initWithScheme:(NSString *)scheme host:(NSString *)host path:(NSString *)pathスキーム、ホストとパスでURLとして新しくつくられたNSURLを初期化して返す
-(id)initWithString:(NSString *)URLStringNSURLを文字で初期化して返す
-(NSURL *)standardizedURL..や.の付いたURLを正規化する


NSURLConnectionはNSURLDownloadに近い動作をするが、 NSURLDownloadが必ずファイルへの書き出しを伴うのに対して、 NSURLConnectionはメモリ上で処理する。 内部動作については ここ参照のこと。

NSURLConnectionには 同期=一気読み込みと非同期=並行動作で読み込みの2つがあるらしい。 前者は終了するまで待つことになるので、そこで処理が止まってしまうことと、それゆえに大きなデータ取得には適さないので、 非同期で取得する方法の方が一般的らしい。プログレスバーを出すなど、途中に処理を追加したい場合も非同期を使う。
NSURLConnection
メソッド名動作
-(id)initWithRequest:(NSURLRequest *)request delegate:(id)delegateリクエストとデリゲートでNSURLConnectionを初期化する
+(BOOL)canHandleRequest:(NSURLRequest *)requestリクエストを扱う事ができるかを返す
-(void)cancel接続をキャンセルする
デリゲートメソッドは以下の通りである。
メソッド名動作
-(void)connection:(NSURLConnection *)connection
didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
didFailWithError:(NSError *)error
didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
didReceiveData:(NSData *)data
didReceiveResponse:(NSURLResponse *)response
didSendBodyData:(NSInteger)bytesWritten totalBytesWritten:(NSInteger)totalBytesWritten totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite
willCacheResponse:(NSCachedURLResponse *)cachedResponse
接続処理途中で呼び出される。
認証キャンセル時
接続できなかった時
認証しなければいけない時
データを受信時
レスポンス受信時
要求のボディ(メッセージデータ)が送られた時(例えばhttpポスト要求において)
レスポンスをキャッシュしようとした時
-(void)connectionDidFinishLoading:(NSURLConnection *)connectionデータのロードが完了したときに呼び出される
これもNSDownload同様メソッドはいろいろあるが、基本的には一定の処理手順を経る経過として呼び出されるだけなので、 ほぼ固定の書き方となる。
// 同期読み込み
#import "Foundation/Foundation.h"

int main()
{
  id pool = [[NSAutoreleasePool alloc] init];

  NSURL *url = [NSURL URLWithString:@"http://www.example.com/"];

  // URL Request
  NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:url];

  // 結果を格納するオブジェクト
  NSURLResponse *resp;

  // エラーを格納するオブジェクト
  NSError *err;

  // 同期的な呼び出し。sendSynchronousRequestが味噌
  // 終了するまで帰ってこない
  NSData *result = [NSURLConnection
                 sendSynchronousRequest:req
                      returningResponse:&resp
                                  error:&err
                   ];

  // 結果をコンソールへ出力。writeはCの標準関数。ただし、この書き方は「例」なので、このままでは動かない(includeもないし)
  write(1, [result bytes], [result length]);

  [pool release];
}
非同期の場合は、デリゲートが必要とされている(initWithRequest:)。
// 非同期読み込み

#import "Foundation/Foundation.h"

// 通信の進捗具合に応じて呼び出される
@interface myUrlConnDelegate : NSObject
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response;
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data;
- (void)connectionDidFinishLoading:(NSURLConnection *)connection;
@end

@implementation myUrlConnDelegate
// レスポンスを受け取った時点で呼び出される。データ受信よりも前なので注意
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
  NSLog(@"didReceiveResponse");
}

// データを受け取る度に呼び出される
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
  NSLog(@"didReceiveData");
}

// データを全て受け取ると呼び出される
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
  NSLog(@"connectionDidFinishLoading");
}
@end

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

  NSLog(@"main");

  NSURLRequest *req;

  req = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.example.com/"]
                         cachePolicy:NSURLRequestUseProtocolCachePolicy
                     timeoutInterval:60.0
        ];

  // 呼び出しを受け取るためのオブジェクトを生成
  myUrlConnDelegate *del = [[myUrlConnDelegate alloc] init];

  // myUrlConnDelegateへdelegateしながら初期化
  NSURLConnection *conn;
  conn = [[NSURLConnection alloc] initWithRequest:req delegate:del];

  if (conn) {
    NSLog(@"main : NSURLConnection create success");
  } else {
    NSLog(@"main : conn is nil");
    return 1;
  }

  // 実際の動作はNSRunLoop=並行動作を行うクラス で行われる
  NSLog(@"main : Before NSRunLoop");
  [[NSRunLoop currentRunLoop] run];

  // このサンプルコードはここへ到達しない

  [pool release];
}

上記サンプルに出てくる NSURLRequest/NSURLResponseは以下のメソッドをもつ。が、とりあえず例の部分だけ「暗記」しておけばいいと思う。

NSURLRequest
メソッド名動作
+(id)requestWithURL:(NSURL *)theURL
なし
cachePolicy:(NSURLRequestCachePolicy)cachePolicy timeoutInterval:(NSTimeInterval)timeoutInterval

URLからNSURLRequestを作る
キャッシュポリシーとタイムアウト間隔でNSURLRequestを作る
-(id)initWithURL:(NSURL *)theURL
なし
cachePolicy:(NSURLRequestCachePolicy)cachePolicy timeoutInterval:(NSTimeInterval)timeoutInterval

URLでURLリクエストを初期化して返す
キャッシュポリシーとタイムアウト間隔でNSURLRequestを初期化して返す
-(NSURLRequestCachePolicy)cachePolicyキャッシュポリシーを返す
NSURLRequestCachePolicy(enum値)
NSURLRequestUseProtocolCachePolicyキャッシュは特定のURLロード要求のために使われる
NSURLRequestReloadIgnoringLocalCacheDataURLからのキャッシュを使わずデータをロードする
NSURLRequestReloadIgnoringCacheData同上
NSURLRequestReloadIgnoringLocalAndRemoteCacheData解析中
NSURLRequestReturnCacheDataElseLoad解析中
NSURLRequestReturnCacheDataDontLoad解析中
NSURLRequestReloadRevalidatingCacheData解析中
-(NSURL *)mainDocumentURLメインドキュメントURLを返す
-(NSTimeInterval)timeoutIntervalタイムアウト間隔を返す
-(NSURL *)URLURLを返す
-(NSDictionary *)allHTTPHeaderFieldsHTTPヘッダを返す
-(NSData *)HTTPBodyボディを返す
-(NSString *)HTTPMethodHTTPメソッドを返す
-(BOOL)HTTPShouldHandleCookiesクッキーを使用するかを返す
-(NSString *)valueForHTTPHeaderField:(NSString *)fieldHTTPヘッダの指定した値を返す

NSURLResponse
メソッド名動作
-(id)initWithURL:(NSURL *)URL MIMEType:(NSString *)MIMEType expectedContentLength:(NSInteger)length textEncodingName:(NSString *)nameURLResponseを初期化して返す
-(long long)expectedContentLength受け取る長さを返す
-(NSString *)suggestedFilename提案されるファイル名を返す
-(NSString *)MIMETypeMIMEタイプを返す
-(NSString *)textEncodingNameテキストエンコーディング名を返す
-(NSURL *)URLURLを返す

なお、MacOS10.6では「initWithFileは使わずinitWithURLを使うべし」というお達しが出ているようなので、 iOSも必要なら、それに準じるべきなのであろう。


リソースプログラミング(ちょっとだけ)、バンドル;NSBundle
リソースとはなんぞや。誤解混じりで超簡単に書けば「プログラム中で、データとなる部分全ての集合体」のことである。 グラフィック、音声などのデータは言うに及ばず、数値や文字列も、これみなリソースなのである。

iOSのユーザーインターフェース(以下UI)設計は、基本的にインターフェースビルダー(以下IB)に任せるようになっている。 IBはUIオブジェクトの配置や色や大きさなど全ての属性、表示文字列などを設定できる。 そを設定するはプログラムのソースとして出力される・・・わけではない。 ソースコードは作られず、nibファイルというものの中に格納される。
「nibファイル」と書いているが、実際の拡張子は.xibである。 このファイルはMacOS Xの元となったNextSTEPにあった「.nib」ファイルに由来するので、 現在でも慣例的に「nibファイル」と呼ばれる。

ということは、プログラム実行時にはそれを読み込まないとUI部が表示されないということでもある。 この様に、プログラム上で固定すべきでない情報を全て「リソース」として外部ファイルに 置いてしまうプログラム方法を、アップルでは特に「リソースプログラミング」と呼んでいるようである。
UI情報だけでなく、アイコンファイル名、GPSを使うかどうかの情報、プログラム名なども全て リソースに格納するようになっており、プログラム名-info.plistにその情報が含まれている。

リソースはプログラム本体とは別に外部にあるため、その変更だけならプログラムを全くいじらず行うことが出来る。 ここで書いたとおり、未実装なメニュー項目を入れておくことも出来る。 また、各国版を作るためのローカライズも(プログラム側をちゃんと設計しておけば)nibファイルの編集だけで出来てしまう。
なかなかすごいことではあるが、逆に、UI設計を全てIBにゆだねるプログラム設計、 ターゲット-アクションを設定するなどがインターフェースビルダーを理解する上で一番難しい場所だとも言える。

UI部分以外のリソースを作るには、 プロパティリスト(Property List)という物を使う。 プロパティリストというのは、NSDictionaryの内容をファイルに落としたものであるが、通常「*.plist」ファイルである。
プロパティリストはXMLフォーマット=テキストファイルでもいいので(デフォルトはバイナリフォーマット)、手で書くこともできるが、 プログラム的に生成出来る場合を除き、「PorpertyList Editor」を使った方が楽である。 プロパティリストに含めることができるデータ型は、Dictionary、Array、String、Data、Date、Number、Boolean である。 Dictionary のデータを作りたいときは、Root を Dictionary に、Array のデータが欲しいときは、Root を Array に 設定する。
そうやって作ったリソースを、パッケージの中に入れてやると、NSBundleを使って取り出すことができる。
NSBundleはアプリケーションファイル内にバンドル(同梱)されているファイルの位置を表すクラスである。 バンドル内には通常以下のようなものが格納される。 そのほか、先に書いたとおりのProperty Listのデータが格納される。 どのアプリケーションでも最低1つのバンドルがあり、メインバンドル(main bundle)と呼ぶ。
NSBundle
メソッド名動作
+(NSBundle *)mainBundleメインバンドルを返す
-(NSString *)bundlePathバンドルディレクトリのフルパス名を返す
-(NSString *)pathForResource:(NSString *)name ofType:(NSString *)extension バンドルのリソースから名前と拡張子でファイルを探してそのパスを返す
-(NSDictionary *)infoDictionaryInfo.plistの含む内容を辞書として返す
-(NSString *)resourcePathリソースを含んでいるレシーバのサブディレクトリのフルパス名を返す

他にもいろいろあるが、iOS上で使う文はこの程度で事足りると思う。
UIKitを使えるようにしたときには、(カテゴリ)により、 以下のメソッドが追加される。
NSBundle UIKit Additions
メソッド名動作
-(NSArray *)loadNibNamed:(NSString *)name owner:(id)owner options:(NSDictionary*)options レシーバーバンドル内のnibファイルの内容を解凍する

まず、pathForResource: ofType: を使って、パスを取得する。 その中身を、Dictionary だったら NSDictionary の dictionaryWithContentsOfFile:を、 Array だったら NSArray の arrayWithContentsOfFile:を使って読み出し格納することができる。
NSBundle* mainBundle;
NSString* fileName;

mainBundle = [NSBundle mainBundle];

// リソースのパスを取得
fileName = [mainBundle pathForResource:@"sample" ofType:@"plist"];
//fileName = [mainBundle pathForResource:@"sample.plist" ofType:nil]; // これでも良い
if (fileName) {
    id dict = [NSDictionary initWithContentsOfFile:fileName];
}
この例では、"sample.plist"というファイルに辞書のデータが入ってると想定している。

リソースプログラミングについては、 「リソースプログラミングガイド」(Resource Programming Guide) も参照のこと。


デフォルト設定;NSUserDefaults
NSUserDefaultsクラスは、超簡単に言えば、「設定値を保存する」ためのクラスである。
アプリケーションの各種設定はアプリケーションでファイルを作ってその中に保存する、 なんてことはしないで、基本はこのクラスで記録するわけである。
NSUserDefaultsクラスは、一般型(float,doube,integer,BOOL値,URL等)とid型にアクセスするためのメソッドを提供している。 デフォルトオブジェクトは内部ではプロパティーリストになっている。 ということは辞書構造なわけであり、各値はキー名(defaultName)とともに設定する。 キー名はNSStringであり、読み出し時はそのキー名で指定する。

NSUserDefaults
メソッド名動作
+(NSUserDefaults *)standardUserDefaultsユーザーデフォルトオブジェクトを返す
-(void)setBool:(BOOL)value forKey:(NSString *)defaultNameBOOL値を設定する
-(void)setDouble:(double)value forKey:(NSString *)defaultNamedouble値を設定する
-(void)setFloat:(float)value forKey:(NSString *)defaultNamefloat値を設定する
-(void)setInteger:(NSInteger)value forKey:(NSString *)defaultNameNSInteger(int)値を設定する
-(void)setObject:(id)value forKey:(NSString *)defaultNameオブジェクトを設定する
-(void)setURL:(NSURL *)url forKey:(NSString *)defaultNameURLを設定する
-(BOOL)boolForKey:(NSString *)defaultName指定キーに割り付けられているBOOL値を返す
-(double)doubleForKey:(NSString *)defaultName指定キーに割り付けられているdouble値を返す
-(float)floatForKey:(NSString *)defaultName指定キーに割り付けられているfloat値を返す
-(NSInteger)integerForKey:(NSString *)defaultName指定キーに割り付けられているNSInterger値を返す
-(id)objectForKey:(NSString *)defaultName指定ユーザーに最初に出現したオブジェクトを返す
-(NSURL *)URLForKey:(NSString *)defaultName指定キーに割り付けられているURL文字列を返す
-(NSArray *)arrayForKey:(NSString *)defaultName指定キーに割り付けられている配列オブジェクトを返す
-(NSData *)dataForKey:(NSString *)defaultName指定キーに割り付けられているデータオブジェクトを返す
-(NSDictionary *)dictionaryForKey:(NSString *)defaultName指定キーに割り付けられている辞書オブジェクトを返す
-(NSString *)stringForKey:(NSString *)defaultName指定キーに割り付けられている文字列を返す
-(NSArray *)stringArrayForKey:(NSString *)defaultName特定キーに割り付けられている文字列の配列を返す
-(void)removeObjectForKey:(NSString *)defaultName指定デフォルトキーを持つ値を削除する
-(BOOL)synchronize内容をディスクに書き込む。
通常でユーザーデフォルトオブジェクトはオンメモリにキャッシュされているが、
これを発行することでディスク上に書き込まれる。
iOS4以降ではユーザーアプリのマルチタスクができるようになったため、
一連のユーザーデフォルトオブジェクト設定が終わったら、
これを発行して内容を確定した方が良いとされている。
-(id)initNSUserDefaultsオブジェクトを現在のユーザーのデフォルトで初期化する
-(id)initWithUser:(NSString *)usernameNSUserDefaultsオブジェクトを指定のユーザーのデフォルトで初期化する
書き込まれるのはアドレスではなく、その時点での実内容である。 従って、書き込んだ後にその与えたクラスなどの内容を変更しても影響しない。


日時;NSDate/NSDateFormatter/NSLocale/NSTimeZone/NSCalender/NSDateComponents
NSDateは日時を操作するクラスである。 日時、特に日付の処理というものは、実は国際化を考える上でかなり面倒な部分である。 日本は年/月/日の順であるが、アメリカは月/日/年だし、欧州は日/月/年である。 英語圏では月を数字ではなく英語名で書く場合も多い(Jan,Febとか)。

なぜこのように地域によって表記が違うかというのは、日に対するものの考え方の違いとして 理解することができると思う。すなわち、日本は毎日が違う日だというものの考え(一期一会)だから年から書く、 アメリカは毎年同じようなことをしているから、年の識別は余り重要視しない、 欧州に至っては、毎月同じようなことをしているので以下略。 まあ、日本には元号があり、しかも明治以降はともかく江戸までは数年で変わることもあったので、 「年の識別が非常に重要だった」というのが本当のところだろうが。

余談はさておき、実際に、組み込み機器を造り、それを海外でも売ろうと思うと、 この日付順への対応は必須となる。で、結構面倒なので、クラスとして用意してくれるなら、 「そりゃ楽だ」というものだ。まあ、実際にはそんなに楽ではないが。 この辺、日本人はよく理解しているようだが、欧米では余り認識がないらしく、 順固定である場合が多い。だからあちら産のソフトはちょいと使いにくいと思うことがある。 これまた余談。

 NSDateは単に日時を記録するだけではなく、後述の並行動作系の投入時間の指定などにも使われる。

NSDate
メソッド名動作
+(id)date現在の日付と時間でNSDateを作る
+(id)dateWithTimeInterval:(NSTimeInterval)seconds sinceDate:(NSDate *)date指定日時から指定秒数後のNSDateを作成して返す
+(id)dateWithTimeIntervalSince1970:(NSTimeInterval)seconds1970年1月1日0時0分0秒から指定した秒数前後した日時を返す
+(id)dateWithTimeIntervalSinceNow:(NSTimeInterval)seconds現在の日時から指定した秒数で日時を作る
即時の場合はseconds=0.0にする
+(id)dateWithTimeIntervalSinceReferenceDate:(NSTimeInterval)seconds2001年1月1日0時0分0秒から指定した秒数前後した日時を返す
+(id)distantFuture遠い未来の日付を返す
特殊な日時状態を設定するときに使う
+(id)distantPast遠い過去の日付けを返す
用途は〃
-(id)initNSDateを初期化する
-(id)initWithTimeIntervalSinceNow:(NSTimeInterval)seconds現在の日付、時間から指定した秒数前後した日時で初期化
-(id)initWithTimeInterval:(NSTimeInterval)seconds sinceDate:(NSDate *)refDate指定日付時間より指定秒数前後した日時を初期化
-(id)initWithTimeIntervalSinceReferenceDate:(NSTimeInterval)seconds2001年1月1日0時0分0秒から指定した秒数前後した日時で初期化
-(id)initWithTimeIntervalSince1970:(NSTimeInterval)seconds1970年1月1日0時0分0秒から現在の日時の間隔で初期化
-(NSTimeInterval)timeIntervalSinceDate:(NSDate *)anotherDate2つの時間差を計算する
-(NSTimeInterval)timeIntervalSinceNow現在の時間との時間差を計算する
-(NSTimeInterval)timeIntervalSinceReferenceDateグリニッジ標準時の2001年1月1日0時0分0秒と現在との差を秒数で返す
-(NSTimeInterval)timeIntervalSince19701970年1月1日0時0分0秒から現在の日時の間隔を返す
-(NSString *)descriptionYYYY-MM DD HH:MM:SS±HHMM 文字列にして返す
-(NSString *)descriptionWithLocale:(id)localeローカライズした文字列にして返す
-(BOOL)isEqualToDate:(NSDate *)anotherDate日時比較をする
-(NSDate *)earlierDate:(NSDate *)anotherDate日時を比較、より古い日時を返す
-(NSDate *)laterDate:(NSDate *)anotherDate日時を比較して、より新しい日時を返す
-(NSComparisonResult)compare:(NSDate *)anotherDate日付を比較してその結果を返す

NSDateで得られた日時を特定のフォーマットやローカライズに対応させて出力したいときは、NSDateFormatterを使う。
主なフォーマット文字列は以下の通り。詳細はこちら。 1文字だけ書いているが、文字を繰り返すことで、その桁数になる、もしくは表記が変わる(「例」HH=00,01~23,H=0,1~23)。 大文字小文字も意味が違う。
主なフォーマット文字列内容
y
M月(01-12)
d日(01-31)
H時間(00-23)
h時間(00-11)
m分(00-59)
s秒(00-59)
w年内の週
W月内の週
ZZZZGMTからの時差(HHmm;例+0900)
zzzzタイムゾーン名
NSDateFormatter *formatter = [NSDateFormatter alloc]; 
[formatter setLocale:[NSLocale currentLocale]];
[formatter setDateFormat:@"HH:mm"];
NSDate *date = [NSDate date];
NSString* dateStr = [formatter stringFromDate:date];
ここで重要なのは、setLocale:でロケール=地域を設定することである。 setDateFormat前にこれをしておかないと、正しい文字列が返らないらしい。 現在設定されているロケールは[NSLocale currentLocale]で取得できるので、そのままsetLocaleに指定する。 日本語の場合はsetLocale:ja_JPらしい。

NSDateFormatter
メソッド名動作
-(void)setLocale:(NSLocale *)localeレシーバーのロケールを設定する
-(NSLocale *)localeレシーバーのロケールを返す
-(void)setDateFormat:(NSString *)string日付フォーマットを設定する
-(NSString *)dateFormatフォーマット形式の文字列を返す
-(NSString *)stringFromDate:(NSDate *)date日時文字列を返す
-(NSDate *)dateFromString:(NSString *)string文字列からNSDateを返す
-(void)setShortMonthSymbols:(NSArray *)array省略形月表記を設定する
-(NSArray *)shortMonthSymbols省略形月表記を取得する
-(void)setMonthSymbols:(NSArray *)array月表記を設定する
-(NSArray *)monthSymbols月記号を取得する
-(void)setShortWeekdaySymbols:(NSArray *)array省略形曜日表記を設定する
-(NSArray *)shortWeekdaySymbols省略形曜日表記を取得する
-(void)setWeekdaySymbols:(NSArray *)array曜日表記を設定する
-(NSArray *)weekdaySymbols曜日表記を取得する
-(void)setAMSymbol:(NSString *)stringAM記号を設定する
-(NSString *)AMSymbolAMシンボルを取得する
-(void)setPMSymbol:(NSString *)stringPM記号を設定する
-(NSString *)PMSymbolPMシンボルを取得する
-(void)setCalendar:(NSCalendar *)calendarカレンダーを設定する
-(NSCalendar *)calendarレシーバーのカレンダーを取得する
-(void)setTimeZone:(NSTimeZone *)tzタイムゾーンを設定する
-(NSTimeZone *)timeZoneタイムゾーンを取得する
NSDateFormatterはスレッドセーフではないらしい。内部に静的ワークを持っているからだと思われる。 マルチスレッドプログラムするときは要注意である。
NSLocale
メソッド名動作
+(id)currentLocale現在のユーザーのロケールを返す
+(id)systemLocaleシステムロケールを返す
+(NSArray *)ISOCountryCodes ISO国コード を返す
+(NSArray *)ISOCurrencyCodesISO通貨コードを返す
+(NSArray *)ISOLanguageCodesISO言語コードを返す

タイムゾーンまでごねごねしたいときは、NSTimeZoneまで持ち出す。 基本的にはsystemTimeZoneで取得してNSDateFormatter setTimeZone:に与えるだけでよい。
NSTimeZone
メソッド名動作
+(NSTimeZone *)systemTimeZoneシステムによって現在使われているタイムゾーンを返す
+(id)timeZoneWithAbbreviation:(NSString *)abbreviation省略形からタイムゾーンを作る(日本標準時の省略形は"JST")
+(id)timeZoneForSecondsFromGMT:(NSInteger)secondsグリニッジ標準時からの秒差でタイムゾーンを作る
-(NSString *)abbreviationレシーバのタイムゾーンの省略形(日本標準時ならJST)を返す
-(NSString *)abbreviationForDate:(NSDate *)aDate指定した日付でタイムゾーンの省略形を返す
-(NSString *)nameタイムゾーンの地域名を返す
-(NSInteger)secondsFromGMTレシーバーのタイムゾーンとグリニッジ標準時との間隔を秒で返す
-(NSInteger)secondsFromGMTForDate:(NSDate *)aDate指定した日付でタイムゾーンとグリニッジ標準時との間隔を秒で返す
-(BOOL)isDaylightSavingTime現在はサマータイム(DST)中かを返す
-(BOOL)isDaylightSavingTimeForDate:(NSDate *)aDate指定した日付はサマータイム(DST)中かを返す
-(NSData *)dadataレシーバのタイムゾーンのデータを返す
-(BOOL)isEqualToTimeZone:(NSTimeZone *)aTimeZoneタイムゾーンを比較して同じかを返す
-(NSString *)descriptionタイムゾーンを表す文字列を返す

NSDateFormatter* dateFormatter = [[NSDateFormatter alloc] init];
NSTimeZone* aTimeZone = [[NSTimeZone systemTimeZone] retain];
[dateFormatter setTimeZone:aTimeZone];
[aTimeZone release];
[dateFormatter setLocale:[NSLocale systemLocale]];
[dateFormatter setDateStyle:NSDateFormatterShortStyle];
[dateFormatter setTimeStyle:NSDateFormatterMediumStyle];

NSCalendarは日時情報を操作するクラスである。
NSCalendar
メソッド名動作
+(id)autoupdatingCurrentCalendarユーザー設定の変更に同期するカレンダーを返す
+(id)currentCalendarロケールに従いカレンダーを返す(システムカレンダー設定には同期しない)
-(NSString *)calendarIdentifierカレンダーIDを返す
-(NSDate *)dateByAddingComponents:(NSDateComponents *)comps
 toDate:(NSDate *)date options:(NSUInteger)opts
引数分加算したNSDateを作成する
-(NSDate *)dateFromComponents:(NSDateComponents *)comps引数から絶対日付のNSDateを作成する
-(NSUInteger)firstWeekdayレシーバーの最初の曜日(weekday)番号を返す
-(id)initWithCalendarIdentifier:(NSString *)string与えられたIDで新しいカレンダーオブジェクトを割り付ける
-(NSRange)maximumRangeOfUnit:(NSCalendarUnit)unit指定日付要素の最大値を得る
-(NSRange)minimumRangeOfUnit:(NSCalendarUnit)unit指定日付要素の最小値を得る NSCalendarUnit(enum値)
NSEraCalendarUnit 時代単位(int)
NSYearCalendarUnit 年単位(int)
NSMonthCalendarUnit 月単位(int)
NSDayCalendarUnit 日単位(int)
NSHourCalendarUnit 時単位(int)
NSMinuteCalendarUnit 分単位(int)
NSSecondCalendarUnit 秒単位(double)
NSWeekCalendarUnit 週単位(int)
NSWeekdayCalendarUnit 曜日単位(int)
NSWeekdayOrdinalCalendarUnit週序数単位(int)
NSQuarterCalendarUnit 四半期単位(int)
-(void)setFirstWeekday:(NSUInteger)weekdayレシーバーの最初の曜日番号を設定する
-(NSUInteger)minimumDaysInFirstWeekレシーバーの最初の週の最小日付を得る
-(void)setMinimumDaysInFirstWeek:(NSUInteger)mdwレシーバーの最初の週の最小日付を設定する
-(NSLocale *)localeロケールを返す
-(void)setLocale:(NSLocale *)localeロケールを設定する
-(NSTimeZone *)timeZoneタイムゾーンを返す
-(void)setTimeZone:(NSTimeZone *)tzタイムゾーンを設定する
NSDate *currentDate = [NSDate date];
NSDateComponents *comps = [[NSDateComponents alloc] init];
[comps setMonth:2];
[comps setDay:3];
NSDate *date = [gregorian dateByAddingComponents:comps toDate:currentDate options:0];
[comps release];

NSDateComponents は日時情報そのものを持つクラスである。指定日時からNSDateを直接作ることは出来ず、必ずこのクラスを経由する。
NSDateComponents
メソッド名動作
-(NSCalendar *)calendarレシーバーのカレンダーを得る
-(NSDate *)date日付を得る
-(NSInteger)year年を得る
-(NSInteger)month月を得る
-(NSInteger)day日を得る
-(NSInteger)weekday曜日を得る(1=日~7)
-(NSInteger)week週を得る(解釈はカレンダーによって異なる)
-(NSInteger)weekdayOrdinal週序数を得る(月の第week週の第weekdayOrdinal日)
-(NSInteger)era時代単位を得る
-(NSInteger)hour時を得る
-(NSInteger)minute分を得る
-(NSInteger)second秒を得る
-(NSInteger)quarter四半期を得る
-(NSTimeZone *)timeZoneタイムゾーンを得る
-(void)setCalendar:(NSCalendar *)calカレンダーを設定する
-(void)setYear:(NSInteger)v年を設定する
-(void)setMonth:(NSInteger)v月を設定する
-(void)setDay:(NSInteger)v日を設定する
-(void)setWeekday:(NSInteger)v曜日を設定する(1~7)
-(void)setWeek:(NSInteger)v週を設定する
-(void)setWeekdayOrdinal:(NSInteger)v週序数を設定する
-(void)setEra:(NSInteger)v時代単位を設定する
-(void)setHour:(NSInteger)v時を設定する
-(void)setMinute:(NSInteger)v分を設定する
-(void)setSecond:(NSInteger)v秒を設定する
-(void)setQuarter:(NSInteger)v四半期を設定する
-(void)setTimeZone:(NSTimeZone *)tzタイムゾーンを設定する
// 年月日、時分秒を指定して日時オブジェクトを生成する
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *comps = [[NSDateComponents alloc] init];
[comps setYear:2011];
[comps setMonth:3];
[comps setDay:2];
[comps setHour:14];
[comps setMinute:45];
[comps setSecond:0];
NSDate *date = [calendar dateFromComponents:comps];
NSLog(@"日時=%@", date); // 2011-03-02 14:45:00 +0900
[comps release];


タイマー;NSTimer/NSTimeInterval/NSInvocation/NSMethodSignature
NSTimerはμITRONでいうところの「周期起動」を実現するクラスである。 一定時間毎に処理したい事がある場合はこれを使う。 一番簡単な例は「時計を表示する」とか。 もちろん周期ではなく、一発だけ起動もできる。
タイマー設定の仕方はいくつかある。

NSTimer
メソッド名動作
+(NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)seconds target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)repeats 秒単位で周期起動を設定する。ターゲットはCurrentRunloop(後述)で実行される。
+(NSTimer *)timerWithTimeInterval:(NSTimeInterval)seconds target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)repeats 秒単位で周期起動を設定する。
NSRunLoopのaddTimer:でRunLoopに追加しないと有効にならないので注意。
+(NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)seconds invocation:(NSInvocation *)invocation repeats:(BOOL)repeats 秒単位で周期起動を設定する。ターゲットはCurrentRunloop(後述)で実行される。
NSInvocationクラスで実行オブジェクトを指定する
+(NSTimer *)timerWithTimeInterval:(NSTimeInterval)seconds invocation:(NSInvocation *)invocation repeats:(BOOL)repeats 秒単位で周期起動を設定する。
NSInvocationクラスで実行オブジェクトを指定する。
NSRunLoopのaddTimer:でRunLoopに追加しないと有効にならないので注意。
-(id)initWithFireDate:(NSDate *)date interval:(NSTimeInterval)seconds target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)repeats 指定日時以降に周期的に起動するタイマーを設定
NSRunLoopのaddTimer:でRunLoopに追加しないと有効にならないので注意。
-(void)fireオブジェクトに起動メッセージを送る(強制起動)
-(void)invalidateタイマーを停止する
-(BOOL)isValidタイマーが現在有効かどうか返す
-(NSDate *)fireDateタイマーが最後に呼びだされる時刻を返す
-(void)setFireDate:(NSDate *)dateタイマーが起動する日付・時間を設定する
-(NSTimeInterval)timeIntervalタイマーが呼び出される時間間隔を返す
-(id)userInfo追加情報を返す

主な引数を設定する値は以下の通り。
引数名内容
seconds 周期指定:整数位=秒単位,少数位ms単位
target タイマー起動時にaSelectorのメッセージを送るオブジェクトを指定
このオブジェクトは、タイマーが解放されるまで保持される
aSelectorタイマーの発動時にtargetに送るメッセージ
- (void)timerFireMethod:(NSTimer *)theTimer
このメソッドの引数としてタイマーは自分自身を渡す
userInfoタイマーがaSelectorのメソッドに渡すユーザ情報を指定
このオブジェクトは、タイマーが解放されるまで保持される
nil=指定なし
repeatsYES=繰り返す、NO=一発起動のみ

NSTimeIntervalは1/1000秒=1ms単位で時間を指定する型である(クラスではない)。
測定開始時
    NSDate *startTime = [NSDate date];

経過時間取得時
    NSTimeInterval elapsedTime = [startTime timeIntervalSinceNow]; // 現在の時刻とstartTimeの時間差を算出
    NSString* str = [NSString stringWithFormat:@"経過時間:%f", -elapsedTime]; // 現在時間の方が後なので≦0になるので-している

NSInvocationは、起動するオブジェクトを指定するためのクラスである。 NSTimerで起動するオブジェクトを保持するが、この内容を変更することで、 同じタイマーで異なるオブジェクトの起動が可能になる。 初期状態では引数や返値が保持されないので、必要ならretainArgumentsで保持指定をしておく。
NSInvocation
メソッド名動作
-(void)setTarget:(id)anObject起動オブジェクトにターゲットを設定する
-(void)setSelector:(SEL)selector起動オブジェクトにセレクタを設定する
+(NSInvocation *)invocationWithMethodSignature:(NSMethodSignature *)signature起動オブジェクトを作る
-(id)target起動オブジェクトのターゲットを返す
-(SEL)selector起動オブジェクトにセットされているセレクタを返す
-(BOOL)argumentsRetained起動オブジェクトは引数を保持するかを返す
-(void)retainArguments起動オブジェクトが引数を保持するように設定する
-(void)invoke起動オブジェクトにセットされているターゲットにメッセージを送信する
-(void)invokeWithTarget:(id)anObjectセットされているターゲットと別のターゲットにメッセージを送信する
-(NSMethodSignature *)methodSignature起動オブジェクトのメソッドシグネチャを返す
-(void)setArgument:(void *)buffer atIndex:(NSInteger)index番号で指定した引数に値をコピーする(arg[atIndex]=値)
-(void)getArgument:(void *)buffer atIndex:(NSInteger)index番号で指定した引数の値をコピーする(arg[atIndex]の値)
-(void)setReturnValue:(void *)buffer起動オブジェクトの戻り値をバッファに入れる
-(void)getReturnValue:(void *)buffer起動オブジェクトを起動した後のリターン値をバッファに入れる

@implementation  MyObject
NSTimer *timer= nil ; // クラス内グローバル変数

-(void) timerControl
{
    // タイマー発動時に呼び出されるメソッド
    ~
}
- (void)startTimer
// タイマーを作成してスタート
{
    // メソッドシグネチャ
    NSMethodSignature* aSignature;
    // 起動オブジェクト
    NSInvocation *invocation;
    // セレクタ
    SEL aSelector = @selector(timerControl);
    // メソッドシグネチャ
    aSignature = [self  methodSignatureForSelector:aSelector ];
    // 起動オブジェクトを作る
    invocation = [ NSInvocation invocationWithMethodSignature:aSignature ];
    // ターゲットをセット
    [ invocation setTarget: self  ];
    // セレクタをセット
    [ invocation setSelector: aSelector ];
    // タイマースタート
    timer = [NSTimer scheduledTimerWithTimeInterval: 1.0 invocation:invocation repeats: YES ];
}
@end

NSMethodSignatureは・・・よくわからん。 タイマー指定時に上記のように使うことだけ覚えておけばいいのだろう。当面は。
「frameLength」などはなかなか興味深いメソッドではある。 組み込み機器ではよくスタックオーバーで「わけの解らんバグ」が出たからなぁ。

NSMethodSignature
メソッド名動作
-(NSUInteger)frameLength引数がスタックの上で占めるバイト数を返す
-(const char *)getArgumentTypeAtIndex:(NSUInteger)indexメソッドの番号で指定した引数のタイプを返す
-(NSUInteger)numberOfArgumentsメソッドシグネチャが持つ引数の数を返す
-(NSUInteger)methodReturnLength戻り値のために必要なバイト数を返す
-(const char *)methodReturnTypeメソッドの戻り値のタイプを表しているC文字列を返す


並行動作;NSRunLoop
NSRunLoopはマウスやキーボードなどのイベント取得処理、NSTimerのタイマー待ち、ファイル入出力やNSURLDownloadなどに おける並行動作指示時の実動作を行う。 一般的に各スレッドで自動的にNSRunLoopオブジェクトが作成されるらしい。
NSRunLoop
メソッド名動作
+(NSRunLoop *)currentRunLoop現在のスレッドの実行ループオブジェクトを返す
+(NSRunLoop *)mainRunLoopメインスレッドの実行ループオブジェクトを返す
-(void)run即時、実行ループを実行
-(void)runUntilDate:(NSDate *)limitDate期限まで実行ループを実行する
-(void)addTimer:(NSTimer *)aTimer forMode:(NSString *)mode実行ループにタイマーを加える
-(void)cancelPerformSelector:(SEL)aSelector target:(id)target argument:(id)anArgument 指定したアクションセレクタ、ターゲット、引数のアクションを中止する
-(void)cancelPerformSelectorsWithTarget:(id)target 指定したターゲットのアクションを中止する
-(void)performSelector:(SEL)aSelector target:(id)target argument:(id)anArgument order:(NSUInteger)order modes:(NSArray *)modes カレントスレッドでメソッドを実行する
-(NSString *)currentMode現在のモードを返す
-(NSDate *)limitDateForMode:(NSString *)mode次にタイマーが実行される時間を返す
-(BOOL)runMode:(NSString *)mode beforeDate:(NSDate *)limitDate期限付きループを実行
-(void)acceptInputForMode:(NSString *)mode beforeDate:(NSDate *)limitDate指定時間まで入力を受け付けないようにする

上記メソッドの引数「forMode」には以下の定数を与える。
定数名動作定義ヘッダー
NSDefaultRunLoopModeNSConnection以外の入力ソースに対する処理を行う;メニュー起動時などは無視されるNSRunLoop.h
NSRunLoopCommonModesメニュー実行中なども起動されるNSRunLoop.h
NSEventTrackingRunLoopModeイベント監視ループNSApplication.h

@implementation MyObject
NSTimer *timer;

-(void)timerControl
// タイマーで起動される処理
{
int i;
    i = [myOutlet intValue] +1 ;
    [myOutlet setIntValue:i];
    [myOutlet display];

}
- (void)myAction:(id)sender
{
    timer = [NSTimer
        timerWithTimeInterval: 1.0f // 1秒単位
                       target: self
                     selector: @selector(timerControl)
                     userInfo: nil
                      repeats: YES 
    ];
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSEventTrackingRunLoopMode];
}

- (void)fire:(id)sender
{
    ~
}
- (void)stop:(id)sender
{
    ~
}

通知;NSNotification/NSNotificationCenter
NSNotificationクラスは、通知の仕組みを使用する時、その通知する内容を保持するクラスである。
NSNotification
メソッド名動作
+(id)notificationWithName:(NSString *)aName object:(id)anObject新規通知を作成する
+(id)notificationWithName:(NSString *)aName object:(id)anObject userInfo:(NSDictionary *)userInfoユーザー情報付きで新規通知を作成する
-(NSString *)name通知の名前を返す
-(id)object通知に割り付けられたオブジェクトを返す
-(NSDictionary *)userInfoレシーバーに割り付けられたユーザー情報の辞書を返す

一方、NSNotificationCenterは、通知という仕組みそのものを使えるようにするクラスである。 これを使うと、あるイベントが発生した時に、同じアプリ内の別クラスでそのイベントを拾いたいうということが 出来る様になる。
NSNotificationCenter
メソッド名動作
+(id)defaultCenterプロセスのデフォルトの通知センターを返す
-(void)addObserver:(id)notificationObserver selector:(SEL)notificationSelector name:(NSString *)notificationName object:(id)notificationSender レシーバーの電報(dispatch)テーブルに通知者を受けるクラス(オブザーバー)を追加する
-(void)postNotification:(NSNotification *)notificationレシーバーに通知を送る
-(void)postNotificationName:(NSString *)notificationName object:(id)notificationSender 通知を新規作成する
-(void)postNotificationName:(NSString *)notificationName object:(id)notificationSender userInfo:(NSDictionary *)userInfo ユーザー情報付きで通知を新規作成する
-(void)removeObserver:(id)notificationObserver レシーバーの電報(dispatch)テーブルからnotificationObserverのオブザーバーを指定しているエントリを全削除する
-(void)removeObserver:(id)notificationObserver name:(NSString *)notificationName object:(id)notificationSender レシーバーの電報(dispatch)テーブルから一致するエントリを削除する
NortificationとKVOはできることが似ているが、 基本的にはKVOはユーザーが使うもの、Notificationはシステムが提供するものという違いがある感じ。 delegateもちょっと似てるが、UIKitの中にはNotificationを使って変更を知らせてくるものもあるので、 そのときはそれに従う必要があると言うことである。
//----------------------------------------------
// キーボード表示/消去通知
//----------------------------------------------
-(void)keyboardNotification:(BOOL)onoff
// キーボード表示通知の設定
{
     if (onoff) { // ON
         NSLog(@"keyboardNotification on");
         // キーボード表示時に通知させる
         [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
         // キーボード消去時に通知させる
         [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardDidHide:) name:UIKeyboardDidHideNotification object:nil];
         //
     } else { // OFF
         NSLog(@"keyboardNotification off");
         // キーボード通知の解除
         [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil];
         // キーボード消去時に通知させる
         [[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardDidHideNotification object:nil];
     }
}

-(void)keyboardWillShow:(NSNotification *)notification
{
    if (keyboardFrameEnd.size.height!=0) {
         NSLog(@"すでに表示中なので何もしない");
         // なぜかキーボードがすでに表示されているにもかかわらず再度通知がかかることがあるので
         return;
    }

    // キーボードの表示完了時の場所と大きさを取得
    keyboardFrameEnd = [[notification.userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue];
    // iOSバグ:横画面において、キーボードwidthとheightが逆になってる(iPadで確認、iPhoneは不明)
    // deviceOrientationではシミュレーター上では方向が得られないので、簡易的に幅の大小で判定している
    CGFloat w=keyboardFrameEnd.size.width;
    if (w<keyboardFrameEnd.size.height) {
        NSLog(@"横画面なのでwidth←→heightを入れ替える");
        keyboardFrameEnd.size.width=keyboardFrameEnd.size.height;
        keyboardFrameEnd.size.height=w;
    }
}

-(void)keyboardDidHide:(NSNotification *)notification
{
    ~
}


エラー処理;NSError
ファイルやURLの処理を始めると必ず必要になるのがエラー処理。 基本的に、内蔵であれ外部であれデバイスにアクセスするときは必ずエラー処理を書かなければならない。 メッセージを出して処理を止めるにしても、回復・回避処理をするにしてもである。
しかし、それをOS標準のものにまかせたり、完全に抜けているプログラムがいかに多いことか。 組み込み機器では、エラーの処理の出来で相手の技量が分かってしまう。 特に台湾や韓国の安物系ではこれがいい加減なものが多くて、あとで色々困るハメになることが多い。 iOSでは非常に詳細なログを取られているので必要ないかもしれないが、エラー発生時の情報収集も エラー処理の重要な役割である。何がどうなってエラーが発生したか。 それが追えれば、対策はかなり楽になることもある。 はっきり言えば「ユーザーや営業の言葉ほどアテにならないことはない」ので、 機械に状況を記録させて、それを回収して解析するのが一番なのである。

余談はともかく、Cocoa Touchでは、絶対にエラーが発生する可能性があるクラスのため、 その情報を返すためのエラー専用のクラスをもうけている。
NSError
メソッド名動作
+(id)errorWithDomain:(NSString *)domain code:(NSInteger)code userInfo:(NSDictionary *)dictエラーオブジェクトを作る
-(id)initWithDomain:(NSString *)domain code:(NSInteger)code userInfo:(NSDictionary *)dictエラーオブジェクトを初期化して返す
-(NSInteger)codeエラーコードを返す
-(NSString *)domainドメインを返す
-(NSDictionary *)userInfo追加情報を返す
-(NSString *)localizedDescriptionエラーを説明する文字列を返す

まあ、一応このようなメソッドが存在するが、これらを使うのは自前でエラーを書くときだけである。 標準で用意されているクラスを使うときは、返ってくるエラー情報を得るために、引数として与えるのがほとんどだろう。 NSErrorはこれだけで使うことはない。エラー処理を指定できるメソッドに引数として渡すことになる。
BOOL fileCopy(NSString *src,NSString *des)
{
    NSLog(@"fileCopy:%@ → %@",src,des);
    
    NSFileManager *fp=[NSFileManager defaultManager];
    NSError *error;
    BOOL ret=[fp copyItemAtPath:src toPath:des error:&error];
    if (!ret) {
        NSLog(@"error=%@",error);
    }
    return ret;
}
この例ではまずリターン値でエラーの発生を調べ、発生しているときのみerrorを読み出している。 引数として渡したときのNSErrorは、エラー発生時出ないと内容が設定されないことがある。 従って、errorがnilかどうかでエラーを判定してはいけない。
    if (error!=nil) {
        NSLog(@"error=%@",error);
    }
と書いてはいけないと言うことだ。 もしそう書きたければ、
    NSError *error = nil; // これが重要
    BOOL ret=[fp copyItemAtPath:src toPath:des error:&error];
    if (error!=nil) {
        NSLog(@"error=%@",error);
    }
としなければならない。


例外処理;NSException
「例外処理」とは、通常の処理を強制的に打ち切って別処理へ飛ばす機能だと思えばよい。 基本的には例外に飛んだ後元の処理に戻すことも出来る。
昔のMS-BASICのon error goto/error/resumeに近いし(X-BASICには存在しない)、 68000CPUにおけるTRAP命令に近いと思えばよい。
NSExceptionは例外を管理するクラスである。
NSException
メソッド名動作
+(NSException *)exceptionWithName:(NSString *)name reason:(NSString *)reason userInfo:(NSDictionary *)userInfo例外を作る
-(void)raise例外を発生させる
+(void)raise:(NSString *)name format:(NSString *)format, ... フォーマット付き文字列情報を持って例外を発生させる
-(id)initWithName:(NSString *)name reason:(NSString *)reason userInfo:(NSDictionary *)userInfo例外を初期化する
-(NSString *)name例外名を返す
-(NSString *)reason例外の理由を返す
-(NSDictionary *)userInfo例外の追加情報を返す

例外の強制発生は下のように行う。
- (void)foo
{
    ~
    if (result == -1) {
        [[NSException exceptionWithName:@"FooException"         // 例外名
                                 reason:@"Result returned -1.." // その理由
                               userInfo:nil]                    // 情報
            raise];
    }
}

または
{
NSException *matrixException;
        matrixException = [[NSException alloc]
                                initWithName:@"Exception"
                                      reason:@"reason"
                                    userInfo:nil
                          ];
        [matrixException raise];
}
例外を受ける側は「Objective-Cの例外処理」で書いたとおりである。
- (void)bar
{
@try {
    [self foo]; // ここで例外が発生したら
}
@catch (NSException *exception) { // ここで受ける
    NSLog(@"main:Caught %@:%@", [exception name], [exception reason]);
    // localExceptionというCの文字列変数(char*)にも例外名が代入されている
    printf("%s\n", [[localException name] cString]);
    // 別の例外処理に投げるとき
    // @throw localException;
}
@finally { // 例外がなくてもここは実行される
    printf("Here is normal.\n");
}
もし、bar内で例外を@catchしないと、barを呼び出したメソッドに例外が渡る。

例外処理中にメソッドを中断してしまいたい=そこで呼び出し側に戻りたいときは、以下のマクロを使う。
マクロ名動作
NS_VOIDRETURNリターン値なしで戻る
NS_VALUERETURN()リターン値付きで戻る(引数,型)
これらはマクロなので、プリプロセッサによって実際のコードに展開されている。
@catch (NSException *exception) { // ここで受ける
    ~
    NS_VALUERETURN(@"Error", NSString *);
    // NS_VALUERETURN(-1, int);
}
スレッドの途中で捕捉されなかった例外は、最終的に実行環境に用意されたデフォルトの例外ハンドラがこれを捕まえて、 メッセージを出す。通常のアプリケーションではその後、イベントループに戻るのでそのまま動くことが多いと考えられるが、 基本的にこの状態はバグなので好ましくない。

このデフォルトのハンドラを別なものと置き換えるための、関数が用意されている。
void NSSetUncaughtExceptionHandler(NSUncaughtExceptionHandler *handler);

void MyUncaughtExceptionHandler(NSException *exception)
// 自前の例外処理
{
#if 0 // 超簡単
    printf("uncaught %s\n", [[exception name] cString]);
#else // スタック内容を表示
    NSArray *callStackArray = [exception callStackReturnAddresses];
    int frameCount = [callStackArray count];
    void *backtraceFrames[frameCount];

    for (int i=0; i<frameCount; i++) {
        backtraceFrames[i] = (void *)[[callStackArray objectAtIndex:i] unsignedIntegerValue];
    }
#endif
}

int main()
{
    ~
    NSSetUncaughtExceptionHandler(&MyUncaughtExceptionHandler);
    ~
}
なお、この方法で処理(受信)できるのはあくまで例外だけであって、signalは受信できない。 signalまで処理したいときは以下のように、発生しうる全てのsignalに対してトラップ処理を書くしかない。 (もちろんシグナル名を配列にして処理を短くすることは出来るけど、意味は同じ。)
#include <signal.h>

void mysighandler(int sig, siginfo_t *info, void *context)
{
void *backtraceFrames[128];
    // backtrace()はMacOSのUnix部分の関数
    int frameCount = backtrace(backtraceFrames, 128);
    // report the error
}

int main(int argc, char *argv[])
{
      struct sigaction mySigAction; // 関数と同名だが、こちらは構造体(Objective-Cの命名規則参照)
      mySigAction.sa_sigaction = mysighandler;
      mySigAction.sa_flags = SA_SIGINFO;
//
      sigemptyset(&mySigAction.sa_mask);
      sigaction(SIGQUIT, &mySigAction, NULL); // こちらは関数
      sigaction(SIGILL , &mySigAction, NULL);
      sigaction(SIGTRAP, &mySigAction, NULL);
      sigaction(SIGABRT, &mySigAction, NULL);
      sigaction(SIGEMT , &mySigAction, NULL);
      sigaction(SIGFPE , &mySigAction, NULL);
      sigaction(SIGBUS , &mySigAction, NULL);
      sigaction(SIGSEGV, &mySigAction, NULL);
      sigaction(SIGSYS , &mySigAction, NULL);
      sigaction(SIGPIPE, &mySigAction, NULL);
      sigaction(SIGALRM, &mySigAction, NULL);
      sigaction(SIGXCPU, &mySigAction, NULL);
      sigaction(SIGXFSZ, &mySigAction, NULL);
     
      NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
      int retVal = UIApplicationMain(argc, argv, nil, nil);
      [pool release];
      return (retVal);
}
Xcodeで例外のデバッグを行う方法。

XcodeのBreak Pointを表示した状態で、下の「+」を押して「Add Exception Breakpoint」を選択すると、 例外発生をXcodeでとらえることができる。
これを設定して実行すると、意外な所で例外が発生していることに気がつくこともある。
多くの例外は実行時にアプリを落とす原因となるが、中にはOSが良きに計らってくれるのか、 無視されているものもあるようである。