[iOS] Grand Central Dispatch(GCD)でメインスレッドを使って途中結果を表示

2012/11/15

2017/05/12追記
この記事はだいぶ古い記事なのですがありがたいことにアクセスが多いです。ですがもし今swift3で書くのであれば以下のページを参考にした方がよいと思います。
非常に簡単に書けるようになっています。

>> Grand Central Dispatch Tutorial for Swift 3: Part 1/2
>> Grand Central Dispatch Tutorial for Swift 3: Part 2/2
>> [Swift 3] Swift 3時代のGCDの基本的な使い方

以下元の記事となります。
====

こんにちは。きんくまです。

今回はスレッドのはなしです。

ふつう時間のかかる処理をそのままやってしまうと、アプリケーションの応答が止まってしまって、
「何これ、壊れてんの?」なんて状態になります。
UIなどの画面の更新はメインスレッドで行われています。
画面が止まってしまっているのは、このメインスレッドが時間のかかる処理で占有されてしまっているため、画面の再描画ができないからです。
なのでそうならないようにするためには、時間のかかる処理をメインスレッドとは違う、別スレッドでやれば良いです。

今回の記事はこの本を参考にしました。

今回作ったサンプルです。

ボタンがおいてあります。
gcd_sample_fig1

そいつを押すと、
1. くるくるとインジケーターが回って、別スレッドで変数のカウントを増やす
2. 途中結果を画面に表示して、また変数のカウントを増やす
3. 最後に結果を表示して、インジケーターを止める
ということをやります。

gcd_sample_fig2

別スレッドとメインスレッドの関係をどうやって設定するのかわからんかったので、ググったところCocoaの日々さんで書かれてました。

>> Cocoaの日々: CoreData – マイグレーションを考慮した CoreDataManager パターン

記事内容は違うんですが、やり方をみていると、Global Dispatch Queueの中でMain Dispatch Queueを呼び出せばよいみたいです。ていうか、本見直したらそういうコードが載ってた。

ちなみに
・Main Dispatch Queue(メインスレッド)
・Global Dispatch Queue(1から自分でスレッドを作らなくても優先度を指定するだけでうまいことやってくれる別スレッド)
です。

話を戻して、さきほどのサンプルの具体的なコードです。

ARC使用, modern Objective-c
KKViewController.h

#import <UIKit/UIKit.h>

@interface KKViewController : UIViewController
{
    IBOutlet UIButton *_startButton;
    IBOutlet UIActivityIndicatorView *_indicatorView;
    IBOutlet UITextView *_logView;
}
- (IBAction)startButtonTapped:(id)sender;
@end

KKViewController.m


#import "KKViewController.h"

@implementation KKViewController

- (IBAction)startButtonTapped:(id)sender
{
    _logView.text = @"Now Proccessing....";
    [_indicatorView startAnimating];
    _startButton.hidden = YES;
    
    dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    dispatch_async(globalQueue, ^{
        
        //時間のかかる処理
        int count = 0, i;
        for(i = 0; i < 10000; i++){
            NSLog(@"count = %d", count);
            count++;
        }
        
        //メインスレッドで途中結果表示
        dispatch_async(mainQueue, ^{
            [self appendLog:[self logStringWithCount:count]];
        });
        
        //時間のかかる処理
        for(i = 0; i < 10000; i++){
            NSLog(@"count = %d", count);
            count++;
        }
        
        //メインスレッドで終了処理
        dispatch_async(mainQueue, ^{
            [self appendLog:[self logStringWithCount:count]];
            [_indicatorView stopAnimating];
            _startButton.hidden = NO;
        });
    });
}

- (NSString *)logStringWithCount:(int)count
{
    return [NSString stringWithFormat:@"count is %d", count];
}

- (void)appendLog:(NSString *)log
{
    _logView.text = [NSString stringWithFormat:@"%@\n%@", _logView.text, log];
}

@end

こんな感じに、別スレッドの中でメインスレッドをdispatch_asyncで呼び出せばOKみたいです。
時間のかかる処理をする必要があったんで調べてみたんだけど、
GCDを使うと今回みたいなケースでは結構簡単にスレッドが使えて便利ですね。

今回のプロジェクトデータです。


自作iPhoneアプリ 好評発売中!
フォルメモ - シンプルなフォルダつきメモ帳
ジッピー電卓 - 消費税や割引もサクサク計算!

ページトップへ戻る