【Objective-C】初心者でも理解できるdispatch_asyncの基本7選

Objective-Cを使用したプログラミングのスクリーンショット、非同期処理のコード例Objctive-C
この記事は約20分で読めます。

 

【サイト内のコードはご自由に個人利用・商用利用いただけます】

この記事では、プログラムの基礎知識を前提に話を進めています。

説明のためのコードや、サンプルコードもありますので、もちろん初心者でも理解できるように表現してあります。

基本的な知識があればカスタムコードを使って機能追加、目的を達成できるように作ってあります。

※この記事は、一般的にプロフェッショナルの指標とされる『実務経験10,000時間以上』を凌駕する現役のプログラマチームによって監修されています。

サイト内のコードを共有する場合は、参照元として引用して下さいますと幸いです

※Japanシーモアは、常に解説内容のわかりやすさや記事の品質に注力しております。不具合、分かりにくい説明や不適切な表現、動かないコードなど気になることがございましたら、記事の品質向上の為にお問い合わせフォームにてご共有いただけますと幸いです。
(送信された情報は、プライバシーポリシーのもと、厳正に取扱い、処分させていただきます。)

はじめに

皆さんは「Objective-Cで非同期処理を実行する方法を学びたい」と考えたことはありませんか?

この記事を読むことで、初心者でも理解できるObjective-Cの基本をしっかりと学べます。

dispatch_asyncを使ったサンプルコードを通じて、スムーズにコードを書けるようになるためのステップを7つご紹介します。

ここでは、実際にプログラムを書きながら、なぜそのコードを書くのか、どのような結果が得られるのかを詳細に説明します。

これにより、プログラミングスキルが向上し、より複雑なプロジェクトにも自信を持って取り組むことができるようになります。

●Objective-Cとは

Objective-Cは、AppleのOS XやiOSの開発に広く使用されているプログラミング言語です。

C言語に基づいており、Smalltalk言語のオブジェクト指向の特性を取り入れています。

そのため、C言語の強力な機能とオブジェクト指向プログラミングの柔軟性を兼ね備えています。

Objective-CはiOSアプリ開発の主力言語であった歴史があり、現在でも多くのアプリケーションでそのコードが利用されています。

○Objective-Cの歴史と特徴

Objective-Cは1980年代初頭にBrad CoxとTom Loveによって開発されました。

AppleがNeXTを買収したことで、その開発環境と共にObjective-CはAppleのソフトウェア開発に欠かせないものになりました。

Objective-Cの最大の特徴は、動的タイピングをサポートしている点です。

これにより、開発者は柔軟なコーディングが可能となり、プログラムの再利用性とメンテナンス性が向上します。

○Objective-Cの基本構文

Objective-Cの構文は、C言語とオブジェクト指向機能が組み合わさった形になっています。

たとえば、クラスの宣言やメソッドの定義は、他のオブジェクト指向言語と似ていますが、メッセージ送信の構文([receiver message])は独特のものです。

また、ファイルの拡張子は、ヘッダ(.h)ファイルと実装(.m)ファイルに分かれており、.hファイルにはインターフェースの定義を、.mファイルには実装の詳細を書きます。

●dispatch_asyncの基本

プログラミングにおいて、非同期処理はとても重要な概念です。

Objective-Cでの非同期処理の書き方を学ぶことは、効率的なアプリケーションを作成するために必須と言えます。

非同期処理を理解し、適切に利用することで、アプリケーションはスムーズに動作し、ユーザーに快適な体験を提供できます。

Objective-Cにおける非同期処理の実装には、dispatch_asyncがよく使用されます。

この機能は、Grand Central Dispatch (GCD) という技術の一部で、複数のタスクを同時に、かつ効率的に処理することができるように作られています。

dispatch_asyncは、指定した処理をバックグラウンドスレッドで実行し、その結果をメインスレッドで受け取る際に使われることが一般的です。

これにより、ユーザーインターフェースがフリーズすることなく、長時間実行する処理やリソースを多く消費するタスクを扱うことができます。

○dispatch_asyncとは何か?

dispatch_async関数は、処理を非同期でキューに送るための関数です。

キューに送られた処理は、現在実行中のタスクの完了を待たずに、システムが処理を開始できる状態になった時に実行されます。

この方法を用いることで、ユーザーインターフェースの更新とデータ処理のロジックを分離し、アプリケーションのレスポンスを改善することが可能になります。

○dispatch_asyncの基本的な使い方

dispatch_asyncの基本的な使い方は次の通りです。

指定されたキュー(dispatch_queue_t)にブロック(処理の単位)を非同期で送ります。

最も一般的な使い方は、処理をバックグラウンドキューに送り、完了後に何らかの更新をメインスレッドで行うパターンです。

// メインキューを取得
dispatch_queue_t mainQueue = dispatch_get_main_queue();

// バックグラウンドキューを生成
dispatch_queue_t backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

// バックグラウンドキューに非同期で処理を送る
dispatch_async(backgroundQueue, ^{
    // ここに重い処理を書く
    NSString *data = [@"Some heavy task" stringByRepeatingString:@"..." count:1000];

    // 重い処理が終わった後にメインキューでUIを更新する
    dispatch_async(mainQueue, ^{
        // UIの更新処理をここに書く
        self.textView.text = data;
    });
});

このコードでは、dispatch_get_global_queueを使ってシステムが提供するバックグラウンドキューを取得し、その後dispatch_asyncを使ってバックグラウンドで行いたい処理をブロックとしてキューに送っています。

その処理が終了した後、同じくdispatch_asyncを使用してメインキューにUIの更新処理を送っています。

これにより、UIの処理はユーザーのアクションに応じてスムーズに行われ、バックグラウンドでの処理実行中でもアプリケーションが応答を止めることがありません。

ここで紹介したサンプルコードを実行すると、重たい処理がバックグラウンドで実行され、その間にもユーザーはテキストビューのスクロールなどの操作を続けることができます。

処理が終われば自動的にテキストビューに結果が反映されるので、処理の完了を待つ間のユーザーのストレスが軽減されます。

●dispatch_asyncを使ったサンプルコード

ここでは、Objective-Cでdispatch_asyncを使用した様々なシナリオのサンプルコードを見ていきます。

これらのコードを通して、非同期プログラミングの基本的なパターンを理解し、実際にどのように使用されるのかを掴むことができます。

○サンプルコード1:バックグラウンドでの処理実行

非同期処理の最も基本的な用途は、バックグラウンドで時間がかかる処理を実行し、その間にメインスレッドをブロックせずにユーザーインターフェイスが反応し続けることを可能にすることです。

ここでは、バックグラウンドで計算処理を実行し、その結果をメインスレッドでユーザーインターフェースに表示する例を紹介します。

// バックグラウンドで実行する計算処理のサンプルコード
dispatch_queue_t backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(backgroundQueue, ^{
    // ここで時間がかかる計算を行う
    NSInteger result = 0;
    for (NSInteger i = 0; i < 1000000; i++) {
        result += i;
    }

    // 計算が終わったらメインキューで結果を表示
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    dispatch_async(mainQueue, ^{
        // ユーザーインターフェースを更新する
        self.resultLabel.text = [NSString stringWithFormat:@"Result: %ld", (long)result];
    });
});

このコードを実行すると、1から100万までの数を合計する計算がバックグラウンドで行われ、その結果がメインスレッドに戻ってきたときにラベルに表示されます。

計算中でもアプリケーションは他のタスクを同時に処理できるため、ユーザーはフリーズすることなく他の操作を続けられます。

○サンプルコード2:メインキューでのUI更新

Objective-CでUIを更新する際は、メインキュー(メインスレッド)で実行する必要があります。

しかし、バックグラウンドでの処理後にUIを更新する必要がある場合、dispatch_asyncを使用してメインキューに戻すことができます。

// ネットワークからデータをフェッチし、UIを更新するサンプルコード
dispatch_queue_t backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(backgroundQueue, ^{
    // ネットワークからデータをフェッチする想定のコード
    NSURL *url = [NSURL URLWithString:@"https://example.com/data"];
    NSData *data = [NSData dataWithContentsOfURL:url];

    // フェッチが終わったらメインキューでUIを更新する
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    dispatch_async(mainQueue, ^{
        // データを表示するUIの更新処理
        self.imageView.image = [UIImage imageWithData:data];
    });
});

この例では、ウェブからデータを取得している間にも、アプリケーションが応答を停止することなく、データ取得完了後にメインスレッドでイメージビューを更新しています。

○サンプルコード3:グループでの非同期処理

dispatch_asyncを使用する際に、複数の非同期処理をグループ化して、すべての処理が終了したことを検知することもできます。

// dispatch_groupを使ったグループ化された非同期処理のサンプルコード
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

// グループに処理を追加する
dispatch_group_async(group, queue, ^{
    // ここに処理Aを書く
});

dispatch_group_async(group, queue, ^{
    // ここに処理Bを書く
});

// すべてのグループの処理が終了したら呼ばれる
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    // すべての処理が終わった後の処理をここに書く
});

このコードにより、複数のタスクを同時に実行し、すべて完了したことを確認した後に、必要な処理をメインスレッドで行います。

これにより、効率的なリソース管理と同期処理が可能となります。

○サンプルコード4:非同期処理とエラーハンドリング

非同期処理では、エラーが発生する可能性もあります。

dispatch_asyncを使って非同期処理を行う際には、エラーハンドリングも考慮する必要があります。

下記のコードは、エラーが発生した場合にメインスレッドでユーザーに通知する例です。

// エラーハンドリングを伴う非同期処理のサンプルコード
dispatch_queue_t backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(backgroundQueue, ^{
    NSError *error;
    // 何らかのエラーが発生する可能性のある処理を実行する
    BOOL success = [self trySomeProcess:&error];

    // メインキューでエラーを処理する
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    dispatch_async(mainQueue, ^{
        if (!success) {
            // エラーが発生した場合のユーザーへの通知処理
            [self showErrorAlert:error];
        }
    });
});

上記のサンプルコードでは、trySomeProcess:メソッドが成功か失敗かにかかわらず、結果をメインスレッドに送り、成功した場合は次のステップへ進み、失敗した場合はエラーアラートを表示します。

●dispatch_asyncの応用例

Objective-Cにおけるdispatch_asyncの使用法をマスターすると、さまざまな応用例に対応できるようになります。

ここでは、ファイルの非同期読み込み、データベースへのアクセス、ネットワークリクエストといった実用的なシナリオをサンプルコードと共に紹介します。

○サンプルコード5:ファイルの非同期読み込み

ファイルI/Oは時間がかかる操作であり、これをメインスレッドで実行するとユーザー体験が損なわれます。

下記のコードでは、ファイルを非同期に読み込み、読み込みが完了したらUIを更新する方法を表しています。

// ファイルの非同期読み込みサンプルコード
dispatch_queue_t backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(backgroundQueue, ^{
    NSString *filePath = [[NSBundle mainBundle] pathForResource:@"largeFile" ofType:@"txt"];
    NSError *error;
    NSString *fileContents = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:&error];

    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    dispatch_async(mainQueue, ^{
        if (error) {
            // エラー処理をここに書く
            NSLog(@"Error reading file: %@", error.localizedDescription);
        } else {
            // ファイルの内容をUIに表示する
            self.textView.text = fileContents;
        }
    });
});

このコードを実行すると、バックグラウンドでファイルが読み込まれ、読み込みが終わるとメインスレッドでテキストビューに内容が表示されます。

エラーがある場合は、それがコンソールにログとして出力されます。

○サンプルコード6:非同期データベースアクセス

データベースへのアクセスも長時間かかる可能性があるため、非同期処理が推奨されます。

ここでは、データベースから情報を非同期に取得し、取得後にUIに表示するサンプルコードを紹介します。

// データベースへの非同期アクセスサンプルコード
dispatch_queue_t backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(backgroundQueue, ^{
    // データベースからデータを取得する想定のコード
    NSArray *fetchedData = [self fetchDataFromDatabase];

    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    dispatch_async(mainQueue, ^{
        // データをUIに表示する処理
        self.dataListView.data = fetchedData;
        [self.dataListView reloadData];
    });
});

ここではfetchDataFromDatabaseという架空のメソッドがデータベースからデータを取得し、その結果をメインスレッドでリストビューに表示するプロセスを表しています。

○サンプルコード7:非同期ネットワークリクエスト

ウェブサービスからデータを取得する際には、通信の遅延が発生することがあります。

これを非同期で行うことで、アプリケーションの応答性を維持することができます。

// 非同期ネットワークリクエストサンプルコード
dispatch_queue_t backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(backgroundQueue, ^{
    // URLセッションを使ってデータを取得する
    NSURL *url = [NSURL URLWithString:@"https://api.example.com/items"];
    NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
        if (error) {
            // エラー処理
            NSLog(@"Network request error: %@", error.localizedDescription);
        } else {
            // データの解析とUI更新
            NSArray *fetchedItems = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
            dispatch_async(dispatch_get_main_queue(), ^{
                // UIの更新処理
                [self updateUIWithFetchedItems:fetchedItems];
            });
        }
    }];
    [task resume]; // タスクを開始する
});

このコード例では、NSURLSessionを使って非同期にネットワークリクエストを行い、完了したらメインスレッドでUIを更新しています。

●注意点と対処法

dispatch_asyncを使用する際には、いくつかの注意点があり、それらを理解しておくことは非常に重要です。

ここでは、Objective-Cにおける非同期処理の際に一般的に直面する問題とその対処法について詳細に解説します。

○適切なキューの選択方法

dispatch_asyncで処理をキューに送る際は、適切なキューを選択する必要があります。

メインキューはUIの更新に使うべきであり、長時間実行するタスクはグローバルキューに送るべきです。

この選択を誤ると、アプリケーションのパフォーマンスが著しく低下する可能性があります。

○パフォーマンスへの影響と最適化

バックグラウンドで実行するタスクは、計算資源を大幅に消費することがあります。

そのため、タスクの優先順位を適切に設定し、必要以上にCPUを占有しないように気をつける必要があります。

また、バックグラウンド処理の完了後には、できるだけ迅速にメインスレッドに制御を戻すようにしましょう。

○デッドロックの回避方法

非同期処理を実装する際は、デッドロックに特に注意する必要があります。

同じキューに非同期のブロックを入れてしまうと、それがデッドロックを引き起こす可能性があります。

これを避けるためには、ブロック内で同期的にメインキューにアクセスするのではなく、常にdispatch_asyncを使ってメインキューにタスクを送るようにしましょう。

// デッドロックを避けるためのサンプルコード
dispatch_queue_t myQueue = dispatch_queue_create("com.example.myqueue", NULL);
dispatch_async(myQueue, ^{
    // 何かの処理を行う

    // メインキューに戻る前に、デッドロックを避けるために非同期でメインキューに送る
    dispatch_async(dispatch_get_main_queue(), ^{
        // メインスレッドでのUI更新などの処理
    });
});

このコードでは、カスタムキューを作成し、そのキューで何かの処理を行った後、メインキューに戻る際には別の非同期処理として実行しています。

これにより、メインスレッドがブロックされることなく、安全にUIを更新することができます。

●カスタマイズ方法

Objective-Cにおけるdispatch_asyncを用いた非同期処理は、様々なカスタマイズが可能です。

開発者は独自のキューを作成したり、既存のキューの動作を変更して、アプリケーションの要件に応じた最適な非同期処理の実装が行えます。

ここでは、dispatch_asyncの動作をカスタマイズする方法について解説します。

○dispatch_asyncの動作をカスタマイズする方法

dispatch_asyncのカスタマイズには、主にキューの作成と設定が含まれます。

独自のキューを作成することで、タスクの優先順位や実行方法を細かく制御できます。

下記のコードは、カスタムキューを作成し、非同期タスクをスケジュールする方法を表しています。

// カスタムキューの作成と使用のサンプルコード
// カスタムシリアルキューを作成する
dispatch_queue_t customQueue = dispatch_queue_create("com.example.customQueue", DISPATCH_QUEUE_SERIAL);

// カスタムキューに非同期タスクを追加する
dispatch_async(customQueue, ^{
    // ここで行いたい重たい処理を書く
    for (int i = 0; i < 5; i++) {
        NSLog(@"Custom Queue Task %d", i);
    }
});

// メインキューに戻る
dispatch_async(dispatch_get_main_queue(), ^{
    // UIの更新などのメインスレッドでの処理をここに書く
    NSLog(@"Main Queue Update");
});

この例では、DISPATCH_QUEUE_SERIALオプションを使用して、一度に1つのタスクだけを処理するカスタムシリアルキューを作成しています。

そのキューにタスクを追加し、完了したらメインスレッドでのUI更新を行っています。

カスタムキューを使用することで、同じタイプの複数のタスクを処理する際にも、それらが互いにブロックしあうことなく、順序良く実行されることを保証できます。

また、キューの作成時にDISPATCH_QUEUE_CONCURRENTオプションを指定することで、複数のタスクを並行して実行するカスタムコンカレントキューも作成できます。

まとめ

この記事を通じて、Objective-Cの基本からdispatch_asyncを用いた非同期処理の応用まで、7つの重要なポイントを見てきました。

dispatch_asyncは、プログラムのパフォーマンスを向上させ、ユーザー体験を豊かにするために不可欠です。

Objective-Cを学ぶ上でこれらの非同期処理の知識は極めて重要であり、習得すれば、より応答性が高く、効率的なアプリケーションを作成することが可能になります。

この記事が提供した情報とサンプルコードが、あなたのObjective-Cでのプログラミングスキル向上の一助となれば幸いです。

プログラミングは実践を通じて学ぶものです。ぜひ、今回学んだことを実際のコードに活かし、多様なプロジェクトで利用してみてください。