SwiftでのRunLoop活用!たった10選のコードで完璧に理解

SwiftとRunLoopを組み合わせたコードのイメージSwift
この記事は約16分で読めます。

 

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

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

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

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

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

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

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

はじめに

あなたはSwiftでの開発中に「RunLoop」の名前を耳にしたことがあるでしょうか。

RunLoopはiOSやmacOSでのアプリケーションの動作を制御する中心的な役割を果たしています。

この記事を読めばSwiftでのRunLoopの利用方法を完璧に理解することができるようになります。

ここでは、RunLoopの基本的な知識から実践的なサンプルコードまで、10の例を交えて解説していきます。

●SwiftのRunLoopとは

RunLoopとは、アプリケーションが特定のイベント(例えば、タッチイベントやタイマーイベント)を待機し、それを処理する仕組みを指します。

簡単に言うと、アプリが何もしていない状態でずっと動作を継続することができる仕組みを提供するものです。

○RunLoopの基本的な概念

RunLoopは、アプリケーションがイベントを受け取るとそれに応じたアクションを実行するための「ループ」です。

例えば、ユーザーが画面をタップした場合、そのタップイベントを受け取り、関連する処理を実行します。

このコードではRunLoopの基本的な利用方法を紹介しています。

この例ではRunLoopの主要なメソッドを使ってイベントの待機と処理を行っています。

import Foundation

// メインスレッドでのRunLoop取得
let mainRunLoop = RunLoop.main

// タイマーを作成して、RunLoopに追加
let timer = Timer(timeInterval: 1.0, repeats: true) { _ in
    print("タイマーが動作中です")
}
mainRunLoop.add(timer, forMode: .default)

上のコードを実行すると、1秒ごとに「タイマーが動作中です」というメッセージが表示されることがわかります。

○RunLoopの仕組み

RunLoopは、一連のイベントソース(タイマーやネットワーク接続など)からのイベントを待機している間、アプリケーションをアイドル状態に保ちます。

イベントが発生すると、そのイベントに関連するタスクや処理が実行されます。

このコードでは、RunLoopがどのようにイベントソースからイベントを受け取り、それに応じて処理を行うのかを表しています。

この例では、タイマーとネットワーク接続の2つのイベントソースを用意して、それぞれ異なるタスクを実行するようにしています。

import Foundation

// メインスレッドでのRunLoop取得
let mainRunLoop = RunLoop.main

// タイマーを作成
let timer = Timer(timeInterval: 1.0, repeats: true) { _ in
    print("タイマーイベント")
}

// タイマーをRunLoopに追加
mainRunLoop.add(timer, forMode: .default)

// ネットワーク接続のイベントを待機
let connection = URLSession.shared.dataTask(with: URL(string: "https://example.com")!) { data, response, error in
    print("ネットワーク接続のイベント")
}
connection.resume()

このコードを動かすと、1秒ごとに「タイマーイベント」と表示されると同時に、「ネットワーク接続のイベント」とも表示されます。

これにより、RunLoopが複数のイベントソースからのイベントを同時に待機し、それぞれのイベントに応じて適切な処理を行っていることが確認できます。

●RunLoopの作成方法

SwiftにおけるRunLoopの作成は、主にメインスレッドとバックグラウンドスレッドの2つの方法があります。

アプリケーションのニーズに応じて、適切な方法でRunLoopを作成・管理することが大切です。

○サンプルコード1:RunLoopの基本的な作成

アプリケーションが動作している間、常にメインスレッド上でRunLoopは動作しています。

このRunLoopは次のように簡単に取得することができます。

import Foundation

// メインスレッドのRunLoopを取得
let mainRunLoop = RunLoop.main

このコードでは、SwiftのRunLoopクラスを使って、メインスレッド上で動作しているRunLoopを取得しています。

この例ではRunLoop.mainプロパティを利用して、アプリケーションのメインスレッドに関連付けられているRunLoopを簡単に取得できます。

実際に上のコードを動かすと特に目に見える変化はありませんが、この後のイベント処理やタイマーの管理に活用することができるようになります。

○サンプルコード2:RunLoopをカスタマイズして作成

一方、バックグラウンドスレッド上で新しいRunLoopを作成する場合は、次の手順で行うことができます。

import Foundation

// バックグラウンドスレッドでのRunLoopの作成と起動
DispatchQueue.global().async {
    let backgroundRunLoop = RunLoop.current
    backgroundRunLoop.run()
}

このコードでは、GCD(Grand Central Dispatch)のDispatchQueue.global().asyncを使ってバックグラウンドスレッド上での処理を非同期で行っています。

その中でRunLoop.currentを使用して、そのスレッドに関連付けられた現在のRunLoopを取得し、run()メソッドを使ってRunLoopを起動しています。

実際にこのコードを動かすと、新しいバックグラウンドスレッド上でRunLoopが動作を開始し、そこでのイベント処理やタスクの管理が可能になります。

これにより、メインスレッド以外のスレッドでもイベントを監視することができるようになります。

●RunLoopの使い方

SwiftのRunLoopは、アプリケーション内のイベントの待機や処理を効率的に行うためのツールです。

そのため、正確な使い方を学ぶことで、アプリケーションのパフォーマンスを大きく向上させることができます。

○サンプルコード3:RunLoopを使ってイベントを待機

通常、アプリケーションはユーザーからの入力やシステムからの通知など、さまざまなイベントを待機しています。

これらのイベントを効率的に待機し、適切に処理するためにRunLoopを使用します。

import Foundation

// イベントを待機するサンプル
let timer = Timer.scheduledTimer(withTimeInterval: 2.0, repeats: false) { _ in
    print("タイマーが発火しました!")
}

RunLoop.current.run(until: Date(timeIntervalSinceNow: 5.0))

このコードでは、Timer.scheduledTimerを使って2秒後にタイマーを発火させるイベントを登録しています。

そして、RunLoop.current.runを使用して5秒間RunLoopを動作させています。

この結果、2秒後に”タイマーが発火しました!”というメッセージが表示されます。

○サンプルコード4:RunLoopを使ってタイマーを管理

Swiftでは、タイマーの管理もRunLoopを通じて行います。

これにより、特定の時間後や定期的に処理を実行することが可能です。

import Foundation

// タイマーを管理するサンプル
let timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
    print("1秒ごとにこの処理が実行されます。")
}

RunLoop.current.run(until: Date(timeIntervalSinceNow: 5.0))
timer.invalidate()

このコードでは、1秒ごとに指定した処理が実行されるタイマーを登録しています。

そして、5秒後にタイマーを無効にしています。

これにより、5秒間の間に5回、”1秒ごとにこの処理が実行されます。”というメッセージが表示されます。

○サンプルコード5:RunLoopでの非同期処理の制御

非同期処理とは、主な処理の流れを止めずに並行して行われる処理のことを指します。

SwiftのRunLoopを使用することで、非同期処理の制御も行えます。

import Foundation

// 非同期処理のサンプル
DispatchQueue.global().async {
    print("非同期処理を開始します。")

    let timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
        print("非同期処理中...")
    }

    RunLoop.current.run(until: Date(timeIntervalSinceNow: 3.0))
    timer.invalidate()

    print("非同期処理を終了します。")
}

このコードでは、非同期処理としてタイマーを起動し、3秒間”非同期処理中…”というメッセージを1秒ごとに表示しています。

その後、タイマーを無効にし、非同期処理が終了することを通知しています。

●RunLoopの応用例

RunLoopの基本的な使用法を学ぶことで、多くのアプリケーションの動作を最適化できるようになりますが、より高度な応用例を学ぶことで、さまざまなシチュエーションでの応答性や効率を向上させることができます。

○サンプルコード6:RunLoopを使ったアニメーションの制御

Swiftのアニメーションは滑らかで自然な動きが求められるため、RunLoopの細かな制御が必要となることがあります。

import UIKit

class AnimationViewController: UIViewController {
    var displayLink: CADisplayLink?
    var startTime: CFTimeInterval = 0

    override func viewDidLoad() {
        super.viewDidLoad()

        // CADisplayLinkを使用してアニメーションを制御
        displayLink = CADisplayLink(target: self, selector: #selector(animate))
        displayLink?.add(to: .current, forMode: .default)
    }

    @objc func animate(displayLink: CADisplayLink) {
        if startTime == 0 {
            startTime = displayLink.timestamp
        }

        let elapsedTime = displayLink.timestamp - startTime

        // アニメーションの進捗を計算して処理
        // ここでは例として、進捗をログに出力
        print("アニメーション進捗: \(elapsedTime)")
    }
}

このコードでは、CADisplayLinkを使用してアニメーションの進捗を制御しています。

これにより、フレームごとのアニメーションの挙動を細かく制御できます。

○サンプルコード7:RunLoopを使用してのデータのロード

データの読み込みも、RunLoopの制御を利用して効率よく行うことができます。

import Foundation

class DataLoader {
    var inputStream: InputStream?

    func loadData(from url: URL) {
        inputStream = InputStream(url: url)
        inputStream?.schedule(in: .current, forMode: .default)
        inputStream?.open()

        var buffer = [UInt8](repeating: 0, count: 4096)

        while let stream = inputStream, stream.hasBytesAvailable {
            let numberOfBytesRead = stream.read(&buffer, maxLength: buffer.count)

            if numberOfBytesRead < 0 {
                // エラーハンドリング
            } else if numberOfBytesRead == 0 {
                // データの終了
                break
            } else {
                // データの処理
                print("読み込んだデータ: \(buffer)")
            }
        }
    }
}

上記のコードでは、InputStreamを使用して、データをバッファリングしながら読み込んでいます。

RunLoopを利用することで、読み込みの処理を効率的に制御することができます。

○サンプルコード8:RunLoopを使った効率的なバックグラウンド処理

多くのアプリケーションでは、バックグラウンドでの処理が求められます。

RunLoopを適切に使用することで、これらの処理を効率的に行うことができます。

import Foundation

class BackgroundProcessor {
    func processInBackground() {
        DispatchQueue.global().async {
            while true {
                // 何らかのバックグラウンド処理

                RunLoop.current.run(until: Date(timeIntervalSinceNow: 1.0))
            }
        }
    }
}

このコードは、1秒ごとに何らかのバックグラウンド処理を行う例を表しています。

RunLoopを利用して、処理の間隔を制御することができます。

●注意点と対処法

SwiftでRunLoopを使う際には、高いパフォーマンスを得るためだけでなく、注意すべきポイントやリスクが存在します。

適切な理解と対処法を知っておくことで、アプリケーションの安定性や効率性を高めることができます。

○RunLoopの過剰な利用とその対処法

RunLoopは非常に強力なツールですが、過度に使用するとアプリケーションの動作が重くなる可能性があります。

特に、大量のタスクやイベントを短時間に処理しようとすると、CPU使用率が増加し、アプリケーション全体のパフォーマンスに影響を与えることが考えられます。

対処法としては、必要なタスクのみをRunLoopに登録することや、重要度の低いタスクはバックグラウンドで実行するなどの方法が考えられます。

また、イベントの頻度を制御することで、CPUの使用率を適切に管理することも可能です。

○サンプルコード9:RunLoopの処理が過度に重い場合の対処

下記のサンプルコードは、タスクの実行頻度を制御する方法を表しています。

import Foundation

class TaskManager {
    var lastExecutionDate: Date?
    let executionInterval: TimeInterval = 5.0 // 5秒ごとにタスクを実行

    func executeTaskIfNeeded() {
        let currentDate = Date()
        if let lastDate = lastExecutionDate, currentDate.timeIntervalSince(lastDate) < executionInterval {
            // 前回の実行から指定した時間が経過していない場合は、タスクをスキップ
            return
        }

        // タスクの実行
        performTask()

        // 最後の実行日時を更新
        lastExecutionDate = currentDate
    }

    func performTask() {
        // 何らかの処理
        print("タスクを実行しました")
    }
}

let manager = TaskManager()
for _ in 1...10 {
    manager.executeTaskIfNeeded()
    Thread.sleep(forTimeInterval: 1.0)  // 1秒待機
}

このコードでは、指定した実行間隔内でのタスクの実行をスキップすることで、CPUの負荷を軽減しています。

10回のループ内でタスクは5秒ごとにのみ実行され、それ以外の場合は実行がスキップされます。

●カスタマイズ方法

SwiftにおけるRunLoopは、デフォルトの動作だけでなく、さまざまなカスタマイズが可能です。

これにより、特定の処理を効率的に実行したり、アプリケーションの要件に合わせた動作をさせることができます。

ここでは、RunLoopのカスタマイズ方法を具体的なサンプルコードをもとに詳細に解説していきます。

○サンプルコード10:RunLoopをカスタマイズして特定の処理のみを実行

このコードでは、特定のモードを持つRunLoopの動作をカスタマイズし、そのモードでのみ特定のタスクを実行する方法を表しています。

import Foundation

let customMode: RunLoop.Mode = RunLoop.Mode(rawValue: "CustomMode")

// 特定のモードでのみ実行されるタスクを作成
let timer = Timer(timeInterval: 2.0, repeats: true) { _ in
    print("カスタムモードでのみ実行されるタスク")
}

RunLoop.current.add(timer, forMode: customMode)

// 10秒間、カスタムモードでRunLoopを実行
let endDate = Date().addingTimeInterval(10.0)
while Date().compare(endDate) == .orderedAscending {
    RunLoop.current.run(mode: customMode, before: endDate)
}

この例では、独自のモードcustomModeを作成しています。

そして、このモードでのみ実行されるタイマータスクを定義しています。

最後に、10秒間だけこのカスタムモードでRunLoopを実行しています。

この間、定義したタスクが2秒ごとに実行されることが確認できます。

RunLoopをカスタマイズすることで、柔軟にアプリケーションの動作を制御することができます。

例えば、特定のバックグラウンドタスクやUI更新など、特定のモードでのみ実行したい処理がある場合にこのようなカスタマイズが非常に役立ちます。

まとめ

SwiftでのRunLoopの活用は、アプリケーションの動作を効率的に制御する上で非常に重要な役割を果たします。

本記事では、RunLoopの基本的な概念から作成方法、使用方法、応用例、注意点、そしてカスタマイズ方法まで、さまざまな観点から詳細に解説しました。

この知識を活用することで、より高品質なSwiftアプリケーションの開発が可能となります。

RunLoopをうまく使いこなすことで、アプリケーションの動作を最適化し、ユーザーにとっての使いやすさや快適さを追求することができるでしょう。

最後まで読んでいただき、ありがとうございました。

Swiftのプログラミングにおいて、RunLoopの知識が皆様の一助となることを願っています。