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にまつわるエトセトラ
それぞれ、想定されている型が異なるため、コンテクストに応じて使い分けるべきです。
通信処理を行う
メソッドをメインスレッドで実行する もご覧ください
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 を変更する処理があった場合、別スレッドから実行するとすぐには変更が反映されないためである。
- 日本語ドキュメント - Apple Developer より「URLローディングシステム プログラミングガイド」
- NSURLSessionのsharedSessionでは、completionHandlerはmain threadで実行されない
- NSURLSessionのメモリリークに気をつける
- NSURLSession のまとめ
――デリゲートメソッドを使ってもう少し複雑なことをしたい場合はこちらを参照。
アプリケーションの動作情報を確認する
Instruments は、一般的にはプロファイラという種類のツールにあたります。プロファイラは、どのオブジェクトがどれくらいメモリを消費しているとか、どのメソッドにどれくらい時間がかかっているとか、外から動きを見ていてもわからない動作情報を集めてくれます。
【iOS/Mac開発】超サクサクアプリへの必須ツール Instruments を使いこなそう
開発中は、アプリをビルドして実行する Run の代わりに Profile を選ぶ。そうすると自動的に Instruments が起動する。何を計測するか選ぶ画面が表示されるので、計測したい項目を選ぶ。
ビューを切り替える
ビューを追加し、切り替えるには概ね次の手順で行う。
- Storyboard に View Controller を追加する
- 追加した View Controller に対応する、 UIViewController を継承したクラスを作る
- Storyboard から、手順 1 で作成した View Controller の Custom Class に、手順 2 で作成したクラスを指定する
- 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