3. 2時間で覚えるObjective-C

  • 特徴
    • C言語にSmalltalkライクなオブジェクト指向機能を持たせたC言語上位互換の言語
    • 同じくC言語をベースにしているC++とは、全く異なる言語仕様
    • @で始まるコンパイラディレクティブでクラス定義などをおこなう
    • メソッド呼び出しは[]で囲まれたSmalltalkライクなメッセージ式
    • Javaのような仮想マシンを持たずネイティブコンパイルされる
  • 歴史
    • 1983年にBrad Coxによって開発され、そのコンパイラやライブラリを支援するためにStepstone社が設立
    • 1988年にNeXT社のワークステーション用OSであるNEXTSTEPの開発言語として採用
    • 現在では主にアップルのMac OS XやiOS上で動作するアプリケーションの開発で利用

Next































メソッド呼びだしとクラス定義

  • メソッド呼びだし

    実行時のメッセージパッシングであり、その時渡されるメッセージ値をセレクタという

    // メッセージの送信
    // 単項メッセージ
    [receiver msg];
    // 引数付きメッセージ。この場合「msg:with:」で一つのセレクタ
    val = [receiver msg: arg1 with: arg2];
    // メッセージの入れ子
    val = [obj1 msg: [obj2 msg]];
    
    
  • クラス定義

    定義部(@interfaceディレクティブで開始)と実装部(@implementationディレクティブで開始)に分かれている

    // クラスの定義
    @interface MyObject : NSObject
    {
    	int val;
    	id obj;
    	double d;
    }
    
    + (void)classMethod: (id)arg;  // クラスメソッド
    - (id)method: (NSObject *)arg1 with: (int)arg2;  // インスタンスメソッド
    @end
    
    
    // 実装
    @implementation MyObject
    + (void)classMethod: (id)arg
    {
    	// 何かの処理
    }
     
    - (id)method: (NSObject *)arg1 with: (int)args2
    {
    	return obj;
    }
     
    // 典型的なinit
    - (id)init
    {
    	self = [super init]; // スーパークラスの呼びだし
    	if (self != nil) {
    		val = 1;
    		obj = [[SomeClass alloc] init];
    	}
    	return self;
    }
     
    // deallocは自身のリソースを解放してからスーパークラスへ処理を渡す
    - (void)dealloc
    {
    	[obj release];
    	[super dealloc];
    }
    @end
    
    

Next































Objective-Cの基本事項のまとめ

  • クラスメソッド(接頭辞+)とインスタンスメソッド(接頭辞-)
    + (void)classMethod: (id)arg;  // クラスメソッド
    - (id)method: (NSObject *)arg1 with: (int)arg2;  // インスタンスメソッド
    
  • インスタンス変数はあるが、クラス変数は無い
    @interface MyObject : NSObject {
    	int val;
    	id obj;
    	double d;
    }
    
    static int shared_val;	// クラス変数の代替手段
    
  • 慣習として新規オブジェクトの生成は+alloc(ファクトリメソッド)で、初期化は-init(イニシャライザ)
    id obj = [[MyObject alloc] init];
    
  • +newは+allocと-initをまとめておこなう
    id obj = [MyObject new];
    
  • 自分自身を示す特殊変数selfとスーパークラスを示す特殊名称super
    - (id)init
    {
    	self = [super init]; // スーパークラスの呼びだし
    	if (self != nil) {
    		val = 1;
    		obj = [[SomeClass alloc] init];
    	}
    	return self;
    }
    
  • オブジェクトを表す汎用型id
    id obj = [[MyObject alloc] init];
    obj = [[MyObject2 alloc] init];
    
  • 保護レベルは@public(公開)、@protected(継承クラスのみ)、@private(インスタンス内のみ)
    @interface MyObject : NSObject {
    @public
        int val;
    @protected
        id obj;
    @private
        double d;
    }
    
  • #includeの強化版#importプリプロセッサディレクティブ(多重includeしてもエラーとならない)
    #import <stdio.h>
    #import <Foundation/Foundation.h>
    
  • 一行コメントに//が使用可能
    /*
     * C言語のコメント
     */
    // 一行コメント
    

Next































Objective-Cのサンプルプログラム

    #import <stdio.h>
    #import <Foundation/Foundation.h>
    
    int
    main(int argc, char *argv[])
    {
    	int rv = 0;
    
    	id pool = [NSAutoreleasePool new];
    	{
    		if (argc != 2) {
    			NSString *msg =
    				[NSString stringWithFormat:
    					@"usage: %s url_string", argv[0]];
    			printf("%s\n", [msg UTF8String]);
    			rv = 1;
    			goto out;
    		}
    
    		NSString *address = [NSString stringWithCString: argv[1]];
    		NSURL *url = [NSURL URLWithString: address];
    		NSURLRequest *request = [NSURLRequest requestWithURL: url];
    		NSURLResponse *response = nil;
    		NSError *error = nil;
    		NSData *data = [NSURLConnection
    		                    sendSynchronousRequest: request
    		                    returningResponse: &response
    		                    error: &error];
    		if ([[error localizedDescription] length] > 0) {
    			NSLog(@"ConnectionError: %@",
    				[error localizedDescription]);
    			rv = 2;
    			goto out;
    		}
    
    		[data writeToFile: @"output.dat" atomically: YES];
    	}
    out:
    	[pool release];
    	return rv;
    }
    

Next































サンプルプログラムの解説

  • NSAutoreleasePoolは自動解放プール機能を提供するクラス
    • オブジェクト生成の際に暗黙的に自動解放プールに登録
    • プール解放のタイミングで登録されたオブジェクトを解放
    	id pool = [NSAutoreleasePool new];
    
    	[pool release];
    
  • NSStringは文字列を扱うクラス
    • @"Hello World"のような文字リテラルはNSStringクラスのインスタンスとして扱えるオブジェクト定数(NSConstantStringクラスのインスタンス)
    	NSString *msg =
    		[NSString stringWithFormat:
    			@"usage: %s url_string", argv[0]];
    	printf("%s\n", [msg UTF8String]);
    
    	NSString *address = [NSString stringWithCString: argv[1]];
    
  • NSURLはURL(Uniform Resource Locator)を扱うクラス
    • URLWithStringメッセージなどを使用してインスタンスを作成する
    	NSURL *url = [NSURL URLWithString: address];
    
  • NSURLRequestはURLをロードするためのリクエストをカプセル化したクラス
    • requestWithURLメッセージなどを使用してインスタンスを作成する
    	NSURLRequest *request = [NSURLRequest requestWithURL: url];
    
  • NSURLConnectionはURLに接続してデータ通信をおこなうクラス
    • 同期通信の場合はsendSynchronousRequest:returningResponse:error:メッセージを使用する
    	NSData *data = [NSURLConnection
    	                    sendSynchronousRequest: request
    	                    returningResponse: &response
    	                    error: &error];
    
  • NSURLResponseはサーバからのレスポンスを管理するクラス
    • nilで初期化しておく
    • レスポンスのContentLengthはexpectedContentLengthメッセージで取得可能
    	NSURLResponse *response = nil;
    
  • NSErrorはエラー情報をカプセル化したクラス
    • nilで初期化しておく
    • エラーメッセージはlocalizedDescriptionメッセージで取得可能
    	NSError *error = nil;
    
    	if ([[error localizedDescription] length] > 0) {
    
  • NSDataはバイナリデータ格納用のクラス
    • NSURLConnectionの同期通信のメッセージの戻り値を格納する
    	NSData *data = [NSURLConnection
    	                    sendSynchronousRequest: request
    	                    returningResponse: &response
    	                    error: &error];
    
    	[data writeToFile: @"output.dat" atomically: YES];
    
  • NSLogはコンソールに文字列を出力する関数
    • 書式はprintf()などで使用するものと互換性あり
    • %@はインスタンスを文字列化したものを出力する書式
    	NSLog(@"ConnectionError: %@", [error localizedDescription]);
    

Next































NSURLConnectionの同期通信と非同期通信

  • 同期通信はクライアントからサーバへリクエスト送信後、レスポンスが返ってくるまで待ち続ける
    	NSData *data = [NSURLConnection
    	                    sendSynchronousRequest: request
    	                    returningResponse: &response
    	                    error: &error];
    
    	// サーバからレスポンスが返ってくるまで停止
    
    	if ([[error localizedDescription] length] > 0) {
    		NSLog(@"ConnectionError: %@",
    			[error localizedDescription]);
    		rv = 2;
    		goto out;
    	}
    
  • 非同期通信はクライアントからサーバへリクエスト送信後、レスポンスを待たずに処理が進む
    	MyConnectDelegate *delegate = [[MyConnectDelegate alloc] init];
    	NSURLConnection *connection = [NSURLConnection
    					connectionWithRequest: request
    					delegate: delegate];
    
    	// サーバからのレスポンスを待たずに処理が進む
    
    	if (!connection) {
    		NSLog(@"Failed to init connection");
    		rv = 2;
    		goto out;
    	}
    
    	[[NSRunLoop currentRunLoop] run];
    
  • 非同期通信をおこなうにはDelegate(メッセージ処理の委譲)を使用する
  • NSConnectionオブジェクトへの入力の処理を待つため、現在のスレッドの実行ループを呼び出す

Next































NSURLConnectionの非同期通信のDelegate

  • NSURLConnectionのconnectionWithRequest:delegate:メッセージでDelegateへ委譲されるメッセージ処理には以下のようなものがある
    /**
     * Instructs the delegate that authentication for challenge has
     * been cancelled for the request loading on connection.
     */
    - (void) connection: (NSURLConnection *)connection
      didCancelAuthenticationChallenge:
        (NSURLAuthenticationChallenge *)challenge;
    
    /*
     * Called when an NSURLConnection has failed to load successfully.
     */
    - (void) connection: (NSURLConnection *)connection
      didFailWithError: (NSError *)error;
    
    /**
     * Called when an NSURLConnection has finished loading successfully.
     */
    - (void) connectionDidFinishLoading: (NSURLConnection *)connection;
    
    /**
     * Called when an authentication challenge is received ... the delegate
     * should send -useCredential:forAuthenticationChallenge: or
     * -continueWithoutCredentialForAuthenticationChallenge: or
     * -cancelAuthenticationChallenge: to the challenge sender when done.
     */
    - (void) connection: (NSURLConnection *)connection
      didReceiveAuthenticationChallenge:
        (NSURLAuthenticationChallenge *)challenge;
    
    /**
     * Called when content data arrives during a load operations ... this
     * may be incremental or may be the compolete data for the load.
     */
    - (void) connection: (NSURLConnection *)connection
      didReceiveData: (NSData *)data;
    
    /**
     * Called when enough information to build a NSURLResponse object has
     * been received.
     */
    - (void) connection: (NSURLConnection *)connection
      didReceiveResponse: (NSURLResponse *)response;
    
    /**
     * Called with the cachedResponse to be stored in the cache.
     * The delegate can inspect the cachedResponse and return a modified
     * copy if if wants changed to what whill be stored.
    * If it returns nil, nothing will be stored in the cache. */ - (NSCachedURLResponse *) connection: (NSURLConnection *)connection willCacheResponse: (NSCachedURLResponse *)cachedResponse; /** * Informs the delegate that the connection must change the URL of * the request in order to continue with the load operation.
    * This allows the delegate to ionspect and/or modify a copy of the request * before the connection continues loading it. Normally the delegate * can return the request unmodifield.
    * The redirection can be rejectected by the delegate calling -cancel * or returning nil.
    * Cancelling the load will simply stop it, but returning nil will * cause it to complete with a redirection failure.
    * As a special case, this method may be called with a nil response, * indicating a change of URL made internally by the system rather than * due to a response from the server. */ - (NSURLRequest *) connection: (NSURLConnection *)connection willSendRequest: (NSURLRequest *)request redirectResponse: (NSURLResponse *)response;
  • 詳細は以下を参照
  • GNUstep Base Library API
    (http://www.gnustep.org/resources/documentation/Developer/Base/Reference/index.html)

    /usr/local/GNUstep/System/Library/Headers/Foundation/

Next































NSURLConnectionのDelegate実装例

    @interface MyConnectDelegate : NSObject
    {
    	// 更新可能なバイナリデータ格納用オブジェクト
    	NSMutableData *result;
    }
    @end 
    
    @implementation MyConnectDelegate
    - (void)connection: (NSURLConnection *)connection
      didReceiveResponse: (NSURLResponse *)response
    {
    	NSLog(@"didRecieveResponse: %@", response);
    	// レスポンスを受け取ったらNSMutableDataインスタンス生成
    	result = [[NSMutableData alloc] init];
    }
    
    - (void)connection: (NSURLConnection *)connection
      didReceiveData: (NSData *)data
    {
    	NSLog(@"didReceiveData");
    	// データを受け取ったらNSMutableDataインスタンスへ追加
    	[result appendData: data];
    }
    
    - (void)connectionDidFinishLoading: (NSURLConnection *)connection
    {
    	NSLog(@"connectionDidFinishLoading");
    	// ロードが完了したらファイルへ出力
    	[result writeToFile: @"output.dat" atomically: YES];
    }
    @end
    

Next































NSURLConnectionでPOSTメソッドのリクエストを送信

  • NSRLRequestの代りにNSMutableURLRequestを使用してPOSTメソッドのリクエストを組み立てる
  • 
    NSString *address = @"http://localhost:8180/roomReservation/login2.jsp";
    NSURL *url = [NSURL URLWithString: address];
    
    // POSTデータを作成
    NSString *param = @"user_name=iwasaki&mail_address=iwasaki@freebsd.org";
    NSData *body = [param dataUsingEncoding: NSUTF8StringEncoding];
    
    // 更新可能なrequest
    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL: url];
    [request setHTTPMethod: @"POST"];
    [request setValue: @"application/x-www-form-urlencoded"
    	forHTTPHeaderField: @"content-type"];
    [request setHTTPBody: body];
    
    NSURLResponse *response = nil;
    NSError *error = nil;
    NSData *data = [NSURLConnection
                        sendSynchronousRequest: request
                        returningResponse: &response
                        error: &error];
    

Next































[演習]Objective-C

  • ミッション1
  • サンプルプログラムで解説したurlget.mは、引数で指定されたURLにアクセスして コンテンツをファイルoutput.datに格納する。 生成されたoutput.datのサイズが正しいかを確認できるよう、レスポンスの ContentLengthをコンソールに出力するよう修正せよ。

     $ ./urlget http://localhost:8180/
    
  • ミッション2
  • urlget2.mはDelegateを使用したサンプルプログラムの非同期通信版である。 GUIアプリケーションでは同期通信をおこなうと画面表示処理が止まってしまうため、 非同期通信をおこなうことが多い。 urlget2.mの実行中にダウンロードの進行状況をパーセント表示するよう、プログラムを修正せよ。

     $ ./urlget2 http://localhost:8180/docs/changelog.html
    [snip]
     44 % completed (116768 / 260027 bytes)
    [snip]
    100 % completed (260027 / 260027 bytes)
    
  • ミッション3
  • urlget2の非同期通信中にエラーが発生した場合でも、プログラムの終了コードは0となってしまう。 実行ループ終了後にDelegateから通信成功/失敗のステータスを取得できるようにし、失敗していた場合に終了コード3となるようにプログラムを修正せよ。

  • ミッション4(おまけ)
  • logincheck.mは会議室予約システムのアカウントログイン確認プログラムである。 http://localhost:8180/roomReservation/login2.jspをターゲットに引数で指定されたuser_nameとmail_addressをPOSTメソッドで送信する。 ログイン後の遷移先コンテンツに文字列「List of rooms」が含まれるか否かでログイン成否を判断している。 現状このプログラムは未完成であるが、正しく動作するようプログラムを修正せよ。

     $ ./logincheck iwasaki iwasaki@freebsd.org
    

Next