iOS アプリ開発はじめました (2)

クラスの書き方

@property を使うと、メンバ変数を定義し、かつアクセサ (getter と setter) を自動生成できる。

すなわち

@interface Bird : NSObject {
	NSString *_name;
}
- (NSString *) name;
- (void)setName:(NSString *)name;
@end

@implementation Bird

- (NSString *) name {
	return _name;
}

- (void) setName: (NSString *)name {
	_name = name;
}

@end

上記のコードは次のように書き換えることができる。

@interface Bird : NSObject
@property (nonatomic) NSString *name;
@end

@implementation Bird
@end

このときメンバ変数として、先頭にアンダーバーのついた _name が定義される。Getter の名前はプロパティ名、setter の名前は set + プロパティ名となる。

@property を使うと括弧内に指定するオフションによって、オブジェクトの参照の保持方法や、スレッドセーフにするかどうかを指定できる。また、読み取り専用にしたり (指定しなければ読み書き可能)、getter/setter の名前を任意のものにすることができる。

nil とは何者か

「ポインタが何も指していない」ことを示し、実体は0です。
それぞれ、想定されている型が異なるため、コンテクストに応じて使い分けるべきです。

  • nil : (id)0 -> オブジェクトを何も指していない
  • Nil : (Class)0 -> クラスを何も指していない
  • NULL : void * -> ポインタを何も指していない
nilにまつわるエトセトラ

通信処理を行う

URL を使ってサーバと通信するには、 Foundation フレームワークに含まれる「URL ローディングシステム」と呼ばれるクラス群を用いる。URL ローディングシステムでは、データの読み込み、サーバへのアップロード、Cookie の管理、キャッシュの制御、認証と証明書の管理などを行える。

URL 上にあるコンテンツを読み込む場合、次の API が利用できる。

  • NSURLSession (iOS 7 以降 / OS X v10.9 以降)
  • NSURLDownload (旧バージョンの OS X)
  • NSURLConnection (旧バージョンの iOS / OS X)

NSURLSession を使った最も簡単な例:

// 取得するコンテンツの URL
NSURL *url = [NSURL URLWithString: @"http://example.com/path/to/content"];

// セッションの設定はデフォルトとする
// 書き換えることで、HTTP ヘッダ、ネットワークの種類 (WiFi/Cellular)、タイムアウト時間、Cookie、キャッシュ、HTTP プロキシなどの設定ができる
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];

// セッションを作成する
// delegateQueue を指定することにより、ハンドラをメインスレッドで実行させる。nil の場合は別スレッドで実行される
NSURLSession *session = [NSURLSession
	sessionWithConfiguration: config
	delegate: nil
	delegateQueue: [NSOperationQueue mainQueue]];

// データの取得を開始する
[[session
	dataTaskWithURL: url
	completionHandler: ^(NSData *data, NSURLResponse *response, NSError *error)
{
	// 取得したデータを文字列に変換する
	NSString *response = [[NSString alloc] initWithData: data  encoding: NSUTF8StringEncoding];

	NSLog(@"Got response %@ with error %@.\n", response, error);
	NSLog(@"DATA:\n%@\nEND DATA\n", response);

	// セッションの後始末をする。これをしないとメモリリークが発生する
	[session invalidateAndCancel];

}] resume];

なぜハンドラをメインスレッドで実行しているかというと、もしここに View を変更する処理があった場合、別スレッドから実行するとすぐには変更が反映されないためである。

アプリケーションの動作情報を確認する

Instruments は、一般的にはプロファイラという種類のツールにあたります。プロファイラは、どのオブジェクトがどれくらいメモリを消費しているとか、どのメソッドにどれくらい時間がかかっているとか、外から動きを見ていてもわからない動作情報を集めてくれます。

【iOS/Mac開発】超サクサクアプリへの必須ツール Instruments を使いこなそう

開発中は、アプリをビルドして実行する Run の代わりに Profile を選ぶ。そうすると自動的に Instruments が起動する。何を計測するか選ぶ画面が表示されるので、計測したい項目を選ぶ。

ビューを切り替える

ビューを追加し、切り替えるには概ね次の手順で行う。

  1. Storyboard に View Controller を追加する
  2. 追加した View Controller に対応する、 UIViewController を継承したクラスを作る
  3. Storyboard から、手順 1 で作成した View Controller の Custom Class に、手順 2 で作成したクラスを指定する
  4. Storyboard 上でセグエ (segue) を作成する、あるいは、コード上から View Controller を表示する

ボタンを押したときに単に画面を遷移するだけなら Storyboard 上でセグエを作成するのが簡単だ。Control キーを押しながらボタンから View Controller へドラッグすると、Action Segue というポップアップが表示される。簡単な画面遷移なら modal を選ぶ。
push は UINavigationController からでないと呼び出せないので注意。ビルドはできるが、実行時に SIGABRT シグナルが送られ動作を停止する。

コード上からビューを遷移する場合は次のようにする。

// 親ビューのメソッド:
ContentViewController *contentViewController = [self.storyboard instantiateViewControllerWithIdentifier:@"ContentView"];
[self presentViewController: contentViewController  animated: YES  completion: nil];

Identifier は次の図に示した場所から設定する。

開いたビューを子ビューから閉じるには次のようにする。

// 子ビューのメソッド:
[self dismissViewControllerAnimated: YES  completion: nil];

Web View で Web ページを表示する

Web ページを単に表示するだけであれば非常に簡単だ。

// 定義部
@interface ContentViewController : UIViewController
@property (weak, nonatomic) IBOutlet UIWebView *contentWebView;
@end

// 実装部
- (void) someMethod
{
	NSURL *url = [NSURL URLWithString: @"http://example.com/path/to/content"];
	[_contentWebView loadRequest: [NSURLRequest requestWithURL: url]];
}

読み込み時にインジケータ (ぐるぐる回るアイツ) を表示したい場合は、UIWebViewDelegate を実装する。

// 定義部
@interface ContentViewController : UIViewController <UIWebViewDelegate>
...
@end

// 実装部
- (void) webViewDidStartLoad: (UIWebView *)webView
{
	[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
}

-(void) webViewDidFinishLoad: (UIWebView*)webView
{
	[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}

また、ビューが非表示になったときにインジケータをオフにする。

- (void) viewDidDisappear: (BOOL)animated
{
	[super viewDidDisappear: animated];

	// インジケータをオフにする
	[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}

Web View で表示したコンテンツ内のリンクをタップしたとき、リンク先が表示中のコンテンツと同じ Origin (スキーム、ホスト、ポートの組み合わせ) ならばそのリンク先に遷移する。異なる Origin のリンク (以下外部リンク) の場合は何も起こらない。
しかしながら、次のデリゲートを設定することでリンクの情報を取得することが可能である。

// ナビゲーション発生時に呼ばれる (リンク、フォーム送信、戻る、進む、リロード、外部リソース読み込み)
- (BOOL) webView: (UIWebView *)webView
	shouldStartLoadWithRequest: (NSMutableURLRequest *)request
	navigationType: (UIWebViewNavigationType)navigationType
{
	// リンクがクリックされた
	if (navigationType == UIWebViewNavigationTypeLinkClicked) {
		NSURL *url = [request URL];
		NSLog(@"Link clicked %@://%@%@", [url scheme], [url host], [url path]);

		if ([@"suzume.cc" compare: [url host]] == NSOrderedSame) {
			// Safari で開く
			[[UIApplication sharedApplication] openURL: url];
		}
	}
}

#pragma mark でメソッドをグループ化

Xcode ではクラスメソッド一覧から各メソッドの記述位置にジャンプする機能があるが、メソッド数が多くなると目的のものを探すのが大変になる。ビューコントローラにデリゲートを設定した場合などはどうしてもメソッド数が多くなってしまう。
そこで #pragma mark を使う。これを使うとメソッド一覧に見出しや区切り線を加えることができる。

// 見出しを加える
#pragma mark Some Heading

// 区切り線を加える
#pragma mark -

// 区切り線と見出しを加える
#pragma mark - Some Heading