CS193p - Lecture 10

iTunes U スタンフォード大学のiOSアプリ開発講義のLecure 10(Block and Multithreading)の講義メモです。

UITabBarController (1:22〜)

保持しているVCは、プロパティでアクセス可能

@property (nonatomic, strong) NSArray *viewControllers;

■タブのタイトル設定

storyboardでタブバーをクリックして選択してからAttributes InspectorでTitleとイメージの設定を行う。それは、UITabBarControllerにリンクしているView ControllerのtabBarItemプロパティを設定している。
    リンクしているView Controller.tabBarItem.title
                                                            .image
                                                            .badgeValue

■5つ以上タブを持つ場合

タブを5つ以上設定した場合は、5つ目にMoreというボタンができるが、これは全て自動で行われるのでコードする必要はない。

■他のコントローラとの組み合わせ

・UINavigationControllerとの組み合わせ

    必ずUINavigationControllerをUITabBarControllerの中に入れる。

・UISplitViewControllerとの組み合わせ
    必ずUITabBarControllerをUISplitViewControllerの中に入れる。
    masterとdetailどちらでもOK。

UINavigationController

UINavigationControllerの上部のバー内にあるアイテムは、UIViewControllerの次のプロパティが持っている。

@property (nonatomic, strong) UINavigationItem *navigationItem;

UINavigationControllerの下部のバー内にあるアイテムは、UIViewControllerのtoolbarItemsプロパティである。

Blocks (9:36〜)

ブロックとは、実行命令であるObjective-cのコードのかたまりのことである。

■構文

^(引数…){
    Objective-Cのコード;
}

 

キャレット(^)で始まり、小括弧で引数を表し、中括弧内にコードが記述される。

例)

[aDictionary enumerateKeysAndObjectsUsingBlock:

                                           ^(id key, id value, BOOL *stop) {
    NSLog(@“value for key %@ is %@”, key, value);
    if ([@“ENOUGH” isEqualToString:key]) {
        *stop = YES;

    }
}];

※enumerateKeysAndObjectsUsingBlockは、ブロックを引数にとるDictionaryのメソッド

■制限事項

ブロックの外で定義されたローカル変数をブロックの中でRead Onlyで使うことができる。

(例外)アンダーバー2つで始まる、__blockでマークされたローカル変数への書き込みは出来る。

__block BOOL stoppedEarly = NO;

 

ブロックの外で作成されたオブジェクトについては、ブロック内でメッセージ送信ができ、プロパティも設定できる。

※ブロックはオブジェクトに対してstrongポインタを持つので、ブロックが終了するまではそのオブジェクトは解放されない。

■タイプの作成

typedef double (^unary_operation_t)(double op);

 

■作成したタイプで変数を定義

unary_operation_t square;

square = ^(double operand) { //squareの引数の値がブロック

    return operand * operand;

}

 

■作成した変数を使う

double squareOfFive = square(5.0); //squareOfFiveは25.0になる

 

■typedefせずに変数定義

上記の変数定義を以下のようにtypedefを使わずに行うことが出来る。

double (^square)(double op) = ^(double op) { return op * op; };

 

しかし、たくさんの括弧で見にくくなるので分かりやすくするためにtypedefを使う。

■定義したタイプを使ったメソッドを定義

@property (nonatomic, strong) NSMutableDictionary

*unaryOperations;

//キーとなる演算子の文字列とブロックを引数にとり、

//それらをDictionaryに登録するメソッド
//※ブロックはオブジェクトのようにDictionaryやArrayに格納できる
- (void)addUnaryOperation:(NSString *)op

          whichExecutesBlock:(unary_operation_t)opBlock

{
    [self.unaryOperations setObject:opBlock forKey:op];
}

//dictionaryからブロックを取り出し、そのブロックを使って演算
- (double)performOperation:(NSString *)operation
{
    unary_operation_t unaryOp =

                    [self.unaryOperations objectForKey:operation];
    if (unaryOp) {
        self.operand = unaryOp(self.operand); //ブロックで演算
    }

    . . .

}

 

■メモリ管理的なこと

ブロックはオブジェクトではないが、同じようにメモリ上に確保され、使い終わったら自動的にその部分のメモリは解放される。

ブロック内のコードで使用するオブジェクトに対して、strongポインタを持つ。

■省略表記による定義

・省略規則

    a) コードから戻り値のタイプが推測される場合は、戻り値は省いて定義出来る
    b) 引数がない場合は、小括弧を省くことが出来る

[UIView animateWithDuration:5.0 animations:^{
    view.opacity = 0.5;
}];

 

■メモリサイクルという罠(メモリリーク)

ブロックを格納した配列をプロパティとして持つ場合、

@property (nonatomic, strong) NSArray *myBlocks; //ブロックの配列

 

そのプロパティに、selfを参照するコードを持つブロックを格納すると、

[self.myBlocks addObject:^() {
    [self doSomething];
}];

 

ブロックはselfに対してstrongポインタを持つし、selfは自身のブロパティに対してstrongポインタを持つのでブロックに対してstrongポインタを持つことになり、お互いがstrongポインタで参照した状態になる。これをメモリサイクルと呼び、どちらもヒープから脱出出来ない状態になる。

・メモリサイクルの解決策
ローカル変数は常にstrongだが、アンダーバー2つで始まる__weakでマークされた変数はweakとして定義される。

__weak MyClass *weakSelf = self;
[self.myBlocks addObject:^() {
    [weakSelf doSomething];
}];

 

iOSでブロックが使われているところ

  • Enumeration
  • アニメーション
  • ソート
  • Notification (事象が発生した場合にブロックを実行する)
  • エラーハンドラ (エラー発生時にブロックを実行する)
  • 終了ハンドラ (何かが終了した時にブロックを実行する)
  • Dispatch (GCD) API

Grand Central Dispatch (33:08〜)

GCDはCのAPIで、ブロックのキューのようなものである。
システムは、別々のスレッドのキューから実行命令を取り出して実行する。
これで何が起こるかは保障出来ないが、知っておけば良いことは、実行命令がブロックされたらブロックされるのはそのキューだけで、他のキューは続けて実行されるということである。
この仕組みを使って、時間がかかるネットワーク越しの処理をメインスレッドから外して別のスレッドで同時並行して行うようにする。

■キューに関するCのAPI

・キューの作成と削除

//シリアルキューの作成

dispatch_queue_t dispatch_queue_create(const char *label, NULL);
//キューの解放

void dispatch_release(dispatch_queue_t);

・キューにブロックを投入

typedef void (^dispatch_block_t)(void);
void dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

・キューの保存

//カレントキューの取得
dispatch_queue_t dispatch_get_current_queue();
//dispatch_releaseされるまでヒープに保持
void dispatch_queue_retain(dispatch_queue_t);
//メインキューの取得
dispatch_queue_t dispatch_get_main_queue();

 

<使用例>

※UIKitは、メインキューでしか実行しないこと。

 

GCDを使った非同期データ取得の実装デモ (45:00〜)