PickerVew によるメニュー選択 (3/3)解決篇2012年11月12日 18:07

解決策はロジックを飛び越えて訪れるものらしい・・・?
もう面倒になって、タイマーのあら捜しをあきらめかけていた。そんな時ふとスレッドのことが気になりだした。UIPickerView も名前どうり UIだからメインスレッドで表示しているからである。そこに横槍を入れるならサブスレッドしかないだろう。早速やってみた。
最初 (1)不思議篇 で示したピッカーの停止した時に呼ばれるデリゲードメソッドの次の行に移動させる部分 (太字) だけをサブスレッドで実行するのだ。

.....
.....
if (nowIndex_ == 0) {
    // 次の行に移動させる
    nowIndex_++;
    [picker_ selectRow:nowIndex_ inComponent:component
        animated:YES];
}

これで決まり!
きちんと書くとこんな感じ・・・

// 停止時のデリゲート
- (void) pickerView:(UIPickerView*)pickerView didSelectRow:(NSInteger)row
 inComponent:(NSInteger)component
{
    // 位置をキープするインスタンス変数
    nowIndex_ = row;
    // ドラムの表示 0の位置を index=0 と想定
    if (nowIndex_ == 0) {
        // 次の行に移動させる
        nowIndex_++;
        [self adjustPickerPosition:nowIndex_]; // 移動メソッドを呼ぶ
    }
}

// ドラムの移動メソッド
- (void)adjustPickerPosition:(NSInteger)row 
{
    NSLog(@"adjusted!");
    // サブスレッド
    NSOperationQueue *queue = [[[NSOperationQueue alloc]init] autorelease];
    [queue addOperationWithBlock:^{
        // 行の表示位置をデーターに合わせる
        [picker_ selectRow:row inComponent:0 animated:YES];
    }];
}
NSOperationQueueクラスを使用しているが、iOS 4以降は内部でGCDを使用しているため結果的には同じである (参照:「詳解 iOS 5プログラミング」) 。これでやっと決定打となった。

簡単に検証してみる
さて、せっかくだから逆方向に検証してみよう。手順は、この NSOperationQueueクラスの mainQueueメソッドで取得したキューを使って、このサブスレッドの中からメインのスレッドで実行してみたらどうなるか? これで最初の時と同じように上手く行かなくなれば理屈は成り立つ。
    .....
    .....
    // サブスレッド
    NSOperationQueue *queue = [[[NSOperationQueue alloc]init] autorelease];
    [queue addOperationWithBlock:^{
        // メインスレッドで実行
        NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
        [mainQueue addOperationWithBlock:^{
            // 行の表示位置をデーターに合わせる
            [picker_ selectRow:row inComponent:0 animated:YES];
        }];
    }];
    .....
さて、これは予想どうりに失敗する。これでメインスレッド同士の競合が原因であることの検証もできたが、ここから先の解析は力不足であるし追求しないことにする。

(おわり)