【Swift】DispatchQueueの使い方

こんにちは、のっくんです。

SwiftでDispatchQueueを実装する方法について解説していきます。

DispatchQueueの使用例

ネットから記事をダウンロードさせるアプリのケースです。

  • メインスレッド:スピナーを表示
  • 別スレッド:ネットから記事をダウンロード

メインスレッドでUIを更新しつつ別スレッドで重い処理を走らせるためには、DispatchQueueが有効です。

スレッドを分けて別々の処理をさせないとどうなるのでしょうか。

メインスレッドのみでネットから記事をダウンロードする処理をしている間、画面はフリーズします。

アプリを使っている人からすると、アプリの立ち上げ時と更新時に画面がフリーズしているように見えます。

なんだか気持ち悪いですよね?

スピナーがクルクル回っていた方が親切です。

DispatchQueueの使い方

let globalQueue = DispatchQueue.global(
              qos: DispatchQoS.QoSClass.userInitiated)
      globalQueue.async { [ weak self] in
      //ファイルをダウンロードするよ
    let mp = MyParse()
      self?.items = mp.startDownload()
      DispatchQueue.main.async {
     //メインスレッドでUIを更新する
        self?.table.reloadData()
      }
}

スレッドには2種類あって、メインスレッドと別スレッドがあります。

.globalは別スレッドで処理させることを意味します。

ファイルをダウンロードさせるような重い処理は別スレッドで行うようにします。

この例では、ネットの記事をダウンロードしています。

メインスレッドで処理させる場合は、.mainと記載します。

メインスレッドでは、UIの更新を行います。

asyncは非同期で処理することを意味しますが、同期して処理する場合にはsyncを指定します。

今回の例の場合、ファイルをダウンロードする処理、UIを更新する処理は、非同期(各スレッドが別々に処理する)で動くようにしています。

グルグル回るスピナーを設定する

次にスピナーを設定します。

ストーリーボードを起動。

ActivityIndicatorViewがありますので、TableViewの上に設置します。

spinnerと言う名前でコードと接続します。接続するにはcontrol+ドラッグです。

画面起動時にスピナーが回るようにするために、viewDidLoad()を以下のようにします。

 override func viewDidLoad() {
    super.viewDidLoad()
    spinner.startAnimating()
    let globalQueue = DispatchQueue.global(
        qos: DispatchQoS.QoSClass.userInitiated)
    globalQueue.async { [ weak self] in
                          //ファイルをダウンロードするよ
        let mp = MyParse()
        self?.items = mp.startDownload()
        DispatchQueue.main.async {
            self?.spinner.stopAnimating()
            self?.table.reloadData()
        }
    }
}

startAnimating, stopAnimatingと言う2種類のコードを挿入しました。

UIの操作ですので共にメインスレッドに挿入する点に注意してください。

リフレッシュ(Pull To Refresh)

画面を下に引っ張ると更新する機能をPull To Refreshと言います。

リフレッシュ時にも同様のコードで、マルチスレッドな処理を実現できますが1点だけ注意があります。

リフレッシュ時には、既に記事が格納されているオブジェクトを空にして新たにダウンロード処理を行い記事をダウンロードします。

以下の処理が走ることになります。

  1. 記事が格納されているオブジェクトの配列を空にする
  2. UI上に記事を更新する
  3. 記事を新たにダウンロードしてオブジェクトの配列に格納する

ここで1が行われた後に2が行われると、Index-out-of-rangeエラーが発生します。

https://stackoverflow.com/questions/44631571/realtime-reload-uitableview-without-index-out-of-range-swift/44753530

解決するには上記のサイトにもあります通り、一時的なオブジェクトの配列を使うことで解決します。

@objc private func refresh(sender: UIRefreshControl){
    let globalQueue = DispatchQueue.global(
        qos: DispatchQoS.QoSClass.userInitiated)
    globalQueue.async { [ weak self] in
        let mp = MyParse()
        let tmp = mp.startDownload()
        DispatchQueue.main.async {
            self?.items = tmp
            self?.table.reloadData()
            sender.endRefreshing()
        }
    }
}

別スレッドで作った配列を、メインスレッドで戻った時に書き直す方法です。

こうすることで、元の記事を表示しつつ、リフレッシュが終わった時に更新することが可能になります。

ABOUTこの記事をかいた人

個人アプリ開発者。Python、Swift、Unityのことを発信します。月間2.5万PVブログ運営。 Twitter:@yamagablog