*

iOSプログラミングのキモ(MainViewController説明 : dispatch_asyncとブロック構文を使った 今どきのお手軽マルチスレッドプログラミング)

公開日: : Apple, iOS, iPad, iPhone, Objective-C, XCode

TwitterやYouTubeといったサービスでは、コンテンツの見出しにサムネイルが付加されることが増えてきました。

画面にサムネイルを表示するには 個別にサムネイル画像を読み込んで、それから画面に表示する手順を踏みます。画像はサムネイルだからコンパクトだといっても複数の画像読み込みはそれなりに時間がかかりますし、場合によってはダウンロードしても使われない(画面に表示されない)こともあります。

IMG_1111
こういった場合 サムネイルを遅延ローディングすることで対応します。画像が欲しくなったタイミングで読み込むことで 無駄な画像の読み込みを抑制します。しかしネットワークプログラミングで注意すべきことは画像の読み込みに時間がかかる場合があることを考慮しなくてはいけません。

アプリではネットワーク処理に関わらず時間のかかる処理を行うと画面が固まってしまい、何の操作も受け付けてくれなくなってしまいます。こういった場合の対処方法としてマルチスレッドプログラミングがあります。

iOSでは MacOSXの流れを受け継ぎ Objective-Cの持つ幾つかのマルチスレッドプログラミング手法が用意されてきました。

今回は 3つ目のdispatch_asyncについての箇所をソースから見ていこうと思います。

dispatch_asyncは残り2つに比べ比較的新しい手法です。iOS4以降で使えるようになったブロック処理の記述を併用することで、NSThreadとperformSelectorInBackground:withObject:にはない利点を享受することが出来ます。

  1. コードの記述量が減る
  2. ローカル変数が使える
  3. 2番の結果、スレッドセーフなコードを書きやすくなる

上記の3つは、バグの少ない効率的なプログラミングを進めていくためには、とても重要なことです。
ですので特に理由がない限り今後マルチスレッドプログラミングをObjective-Cで実装するには dispatch_async を使ってください。

前振りが長くなりましたが、実際のコードを見て行きましょう。

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    if(!videos){
        return [self loadingCell2];
    }
    if (indexPath.row < videos.count) {                  QTubeCell *cell = [tableView dequeueReusableCellWithIdentifier:@"ACell"];         if(!cell){             cell = [QTubeCell getQTubeCellWithVideoModel:(VideoModel*)videos[indexPath.row]];         }                  VideoModel* video = videos[indexPath.row];         MediaThumbnail* thumb = video.thumbnail[0];         cell.videoModel = video;                  cell.titleLabel.text = video.title;         cell.autherNameLabel.text = video.authorName;         cell.playCountLabel.text = [AppDelegate createStringAddedCommaFromInt:];         cell.ratingAverageLabel.text = [NSString stringWithFormat:@"%.0f%%", *20.0f];         //LOG(@"%@#cellForRowAtIndexPath video.videoId=%@ thumb.url=%@",[self class], video.videoId, thumb.url);                  NSDateFormatter *df = [[NSDateFormatter alloc] init];         [df setLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"US"]]; // Localeの指定         [df setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"UTC"]];         if(video.duration >= 3600) [df setDateFormat:@"HH:mm:ss"];
        else [df setDateFormat:@"m:ss"];

        NSDate *now = [NSDate dateWithTimeIntervalSince1970:(NSTimeInterval)video.duration];
        NSString *strNow = [df stringFromDate:now];
        cell.dulationLabel.text = strNow;

        [MainViewController cellDownloadStatusSetting:cell VideoId:];

        UIImage *image = [self imageFromLocal:video.videoId];
        if(image)
            cell.imageView.image = image;
        else{
            cell.spinner = [[UIActivityIndicatorView alloc]
                            initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
            cell.spinner.center = CGPointMake(cell.frame.size.width / 4, cell.frame.size.height / 2);
            cell.spinner.autoresizingMask = UIViewAutoresizingFlexibleTopMargin
            | UIViewAutoresizingFlexibleRightMargin
            | UIViewAutoresizingFlexibleBottomMargin
            | UIViewAutoresizingFlexibleLeftMargin;
            cell.spinner.color = UIColor.lightGrayColor;

            [cell addSubview:cell.spinner];
            [cell.spinner startAnimating];
        }
        if(!image){
            dispatch_queue_t q_global = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
            dispatch_queue_t q_main = dispatch_get_main_queue();

            dispatch_async(q_global, ^{
                NSData *data = [NSData dataWithContentsOfURL:thumb.url];
                UIImage *image = [UIImage imageWithData:data];

                dispatch_async(q_main, ^{
                    if(cell.spinner){
                        [cell.spinner stopAnimating];
                        [cell.spinner removeFromSuperview];
                    }
                    if(image){
                        if ([tableView_.visibleCells containsObject:cell]) {
                            [cell.imageView setImage:image];
                            [cell bringSubviewToFront:cell.imageView];
                            [cell bringSubviewToFront:cell.dulationLabel];
                        }
                    }
                });

                if(video.videoId){
                    NSArray *filePaths = NSSearchPathForDirectoriesInDomains (NSCachesDirectory, NSUserDomainMask, YES);
                    NSString *recordingDirectory = [filePaths objectAtIndex: 0];
                    // Pick a file name
                    NSString *filePath = [NSString stringWithFormat: @"%@/%@.jpg", recordingDirectory, video.videoId];

                    NSFileHandle *fileHandle_ = [NSFileHandle fileHandleForWritingAtPath:filePath];
                    if(!fileHandle_)
                    {
                        [[NSFileManager defaultManager] createFileAtPath:filePath contents:nil attributes:nil];
                        fileHandle_ = [NSFileHandle fileHandleForWritingAtPath:filePath];
                    }
                    // 既存のファイルがあったら サイズをゼロにする
                    [fileHandle_ truncateFileAtOffset:0];

                    [fileHandle_ writeData:data];

                    [fileHandle_ synchronizeFile];
                    [fileHandle_ closeFile];
                }

            });
        }

        return cell;

    }else{
        return [self loadingCell];
    }

}

– (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
メソッドはテーブルセルにコンテンツをセットして行くメソッドで UITableView を使ったプログラミングの際には 必ず実装する必要があります。
dispatch_async関連のコードは 30行目〜73行目 です。

dispatch_queue_t q_global = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_queue_t q_main = dispatch_get_main_queue();

1行目は メインスレッドとは別のバックグラウンド用スレッドに対する処理キューを扱う変数、2行目はメインスレッド用の処理キュー変数です。

            dispatch_async(q_global, ^{
                NSData *data = [NSData dataWithContentsOfURL:thumb.url];
                UIImage *image = [UIImage imageWithData:data];

                dispatch_async(q_main, ^{
                    if(cell.spinner){
                        [cell.spinner stopAnimating];
                        [cell.spinner removeFromSuperview];
                    }
                    if(image){
                        if ([tableView_.visibleCells containsObject:cell]) {
                            [cell.imageView setImage:image];
                            [cell bringSubviewToFront:cell.imageView];
                            [cell bringSubviewToFront:cell.dulationLabel];
                        }
                    }
                });
                if(video.videoId){
                    NSArray *filePaths = NSSearchPathForDirectoriesInDomains (NSCachesDirectory, NSUserDomainMask, YES);
                    NSString *recordingDirectory = [filePaths objectAtIndex: 0];
                    // Pick a file name
                    NSString *filePath = [NSString stringWithFormat: @"%@/%@.jpg", recordingDirectory, video.videoId];

                    NSFileHandle *fileHandle_ = [NSFileHandle fileHandleForWritingAtPath:filePath];
                    if(!fileHandle_)
                    {
                        [[NSFileManager defaultManager] createFileAtPath:filePath contents:nil attributes:nil];
                        fileHandle_ = [NSFileHandle fileHandleForWritingAtPath:filePath];
                    }
                    // 既存のファイルがあったら サイズをゼロにする
                    [fileHandle_ truncateFileAtOffset:0];

                    [fileHandle_ writeData:data];

                    [fileHandle_ synchronizeFile];
                    [fileHandle_ closeFile];
                }

            });

dispatch_asyncの部分のみを抜き出してみました。ここではサムネイルの読み込みとサムネイルの保存・表示を担当しています。よく見てみると dispatch_async が入れ子になっています。dispatch_async(q_main の中は画面の制御に関する処理です。画面の制御は メインスレッドの中で行う必要があるので メインスレッドで行うようになっています。

NSThread や performSelectorInBackground:withObject: で同様な処理を実装しようとしたら 恐らく1.5倍〜2倍程度のコード量になってしまうでしょう。

理由は

  1. メインスレッドの部分、バックグラウンドの部分を 個別にメソッドを記述必要がある
  2. 関連変数を クラス変数にする必要がある
  3. 変数をクラス変数にした結果 スレッドセーフな処理を考慮する部分が増える

また 一連の処理を別々のメソッドに書いてしまうと 実装の思考が分断されてしまい。コーディングのサクサク感が削がれてしまい。書いててあまりおもしろくないです(^^;

dispatch_asyncの効果はかなり大きいものがあります。iOSで並列処理を実装する場合は ぜひ使ってみてください。

関連記事

iOSプログラミングのキモ(行き当たりばったりなプログラミングでも、何とか形にするために守っていること その2)

先週に引き続き、今週も文字中心のエントリーです、今回は 下記3つのことを書いていきます。 M

記事を読む

iOSプログラミングのキモ(iOS7から使えるようになったマルチタスク機能、NSURLSessionUploadTask の困った現象)

このブログでも度々書いてきたFileQ iOS版ですが、今月末に Appleに申請できそうな

記事を読む

iOSプログラミングのキモ(MainViewController説明)

個別の画面のコードについて解説を進めていきます。最初は起動直後の画面であるMainViewContr

記事を読む

iOSプログラミングのキモ(行き当たりばったりなプログラミングでも、何とか形にするために守っていること その1)

FileQ iOS版がリリースされて、のんびりしたい気持ちもありますが、FileQ Android版

記事を読む

iOSプログラミングのキモ

このブログでは、実際に弊社が公開しているアプリのソースコードを使って、iOSプログラミングのキモを解

記事を読む

iOSプログラミングのキモ(2:AppDelegate説明 )

実際の AppDelegate.h、AppDelegate.m のソースコードです AppDe

記事を読む

iOSプログラミングのキモ(Delegate iOSプログラミングで避けて通れないしくみ)

Delegate(委任)の考え方を説明します。iOSのプログラミングでは このDelegateが頻繁

記事を読む

iOSプログラミングのキモ(デバッグをやりやすくするための工夫:Debug.hのインクルード )

前回 紹介した Debug.h は、使う際 以下のようにソースファイル(ここでは拡張子が m のもの

記事を読む

iOSプログラミングのキモ(iOS7から使えるようになったマルチタスク機能、NSURLSessionはこう使え!)

今日はQTubeのソースに関する話題ではなく、現在開発中のアプリで使っているNSURLSession

記事を読む

iOSプログラミングのキモ(1:QTubeアプリの説明 )

ソースの説明の前に、QTubeとはどんなアプリなのかを説明します。 QTubeは Youtub

記事を読む

Message

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)

FileQ Hosting 月額99円 容量1GB


サイト管理 Mezzanine
Django上で動くCMS Mezzanine 用のモジュールを作ってみる その1

Django上で動くCMS Mezzanine上で動く、モジュールを作

ホーム Mezzanine
Django上で動くCMS Mezzanine を インストールする MacOSX Yesemite 編

Mezzanineは Django WEBフレームワーク上で動くCMS

EclipseにGWT(Google Web Toolkit) Plugin for Eclipseを入れようとしてハマった

最近PHPでちょっとした業務システムを作りました。業務システムの特徴と

ブログを半年やった成果を Google Analytics から眺める

今年の1月からブログを書き始め、そろそろ半年が経とうとしています。

母校で特別 講義をやってきました。

少し 間が空いてしまいました(^_^;) ちょっと前になりますが

→もっと見る

mautic is open source marketing automation
PAGE TOP ↑