77
55

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Swift の非同期処理の例(複数パターン)

Last updated at Posted at 2019-10-20

はじめに

こんにちは! iOSエンジニアのやまたつ です☺️
Oshidoriというアプリを個人で開発し、リリースしています!!!Oshidoriというアプリを個人で開発し、リリースしています!!!

非同期処理を書く方法っていくつかあるんだなあと思ってまとめてみたくなったので書きました!
実務だったり、個人アプリで使った非同期処理のパターンを書きたいと思います。
複数通信の時の処理も書きます。

Promise Kit が有能すぎて感動したので Promise Kit 贔屓になってます。ご了承ください。

クロージャを使って非同期処理を書く

hoge.swift
                         // ここがクロージャ ////////////////////
func fetchUser(id: Int, completion: @escaping (User?,Error?)->()) {
  // ここで通信
  userInfoRep.fetch(id) { user, error in
    // 通信終わったら入ってくる
    guard let _ = error else { 
      // エラーの時は nil と、 errorを返す
      completion(nil, error)
      return 
    }
      // ちゃんと値が返ってきたら user と nil を返す
    completion(user, nil)
  }
}

// こんな風に使う
// completionの引数が使えるようになる
fetchUser() { user, error in 
  guard let _ = error else {
    // エラー処理
    return
  }
  self.user = user
  tableView.reload()
}

メリット

  • 慣れたらお手軽にかける

デメリット

  • 処理が複雑になってネストすると追うのが辛い

本当にあった怖い話

半年前くらいに書いたコードを見返したらこれが出てきました。
つっこみどころも満載だし、ネストが深すぎて修正する気すらなくすコード。
これがダメなコードってわかるくらいには成長していました。。。
一番怖いのが、getAllInfo を呼び出しているところで completion の処理を何も書いていないこと。。。


func getAllInfo(messageId: String, completion: @escaping () -> ()) {
        userInfoRep.getUserInfo { (userInfo) in
            self.userInfo = userInfo
            self.roomRep.getRoomInfo(roomId: userInfo.roomId, completion: { (room) in
                self.roomInfo = room
                if userInfo.partnerId == room.partnerId {
                    var imageUrl: URL?
                    imageUrl = URL(string: room.partnerImageUrl)
                    if let url = imageUrl {
                        let tmpImageView = UIImageView()
                        Nuke.loadImage(with: url, options: ImageLoadingOptions(
                            placeholder: UIImage(named: "Oshidori_null"),
                            transition: .fadeIn(duration: 0.33)
                            ), into: tmpImageView, progress: { (response, tmp , tmp1) in


// 呼び出し元はこうなっていた(恐怖)
messageRoomService.getAllInfo(messageId: messageId) {}


DispatchGroup を使う

コメントにどのような処理か書いています。

func fetchEntryDetail(entry: Entry, completion: @escaping (Offer?, User?, Error?) -> Void){
        var offer: Offer!
        var user: User!
        // 非同期のグループ作るよ!!!
        let dispatchGroup = DispatchGroup()
        // 並列で実行できるよ〜
        let dispatchQueue = DispatchQueue(label: "queue", attributes: .concurrent)
        
        // 1つ目の並列処理入ります!
        dispatchGroup.enter()
        dispatchQueue.async {
            entry.offerRef.getDocument { (ss, err) in
                guard err == nil else { completion(nil, nil, err); return }
                guard let ss = ss else { completion(nil, nil, nil); return }
                guard let data = ss.data() else { completion(nil, nil, nil); return }
                offer = Offer(id: ss.documentID, document: data, entryStatus: .entried )
                // 1つ目終わりました〜
                dispatchGroup.leave()
            }
        }
        // 2つ目の並列処理入ります!
        dispatchGroup.enter()
        dispatchQueue.async {
            entry.userRef.getDocument { (ss, err) in
                guard err == nil else { completion(nil, nil, err); return }
                guard let ss = ss else { completion(nil, nil, nil); return }
                guard let data = ss.data() else { completion(nil, nil, nil); return }
                user = User(id: ss.documentID, dict: data)
                // 2つ目終わりました〜
                dispatchGroup.leave()
            }
        }
        
        // 上の二つの処理が終わった時(両方の dispatchGroup.leave() が呼ばれた時)実行される
        dispatchGroup.notify(queue: .main) {
            completion(offer, user, nil)
        }
    }

メリット

  • 複数処理にはむいていそう

デメリット

  • 書き方を覚えるのが少し辛い
  • leave し忘れたりすると、意図しない処理になったりする。気付きにくそう。

以下の記事を読みながら実務でコードを書きました!
わかりやすいです!
Swiftで複数の非同期処理の完了時に処理を行う

Promise Kit

スターも 12,000 もあり、最新更新も1ヶ月前ととても活発なライブラリです!
使ってみた感じとても良かったのでオススメです!!!
こちらドキュメントです!

こんなにネストだらけのコードが、

login { creds, error in
    if let creds = creds {
        fetch(avatar: creds.user) { image, error in
            if let image = image {
                self.imageView = image
            }
        }
    }
}

綺麗にかける!!!!

// 最初に
firstly {
    // ログインして
    login()
// ログインが終わったら
}.then { creds in
    // ログインの結果取得した creds を使う
    fetch(avatar: creds.user)
}.done { image in
    // fetch が終わったら、fetchしたimageを使える
    self.imageView = image
}

Promise Kit スゲ〜〜〜

複数通信もお手の物

クロージャだと

// これが終わったら
operation1 { result1 in
    // 2をやる
    operation2 { result2 in
        // 2が終わったらこれ。ネスト。。。
        finish(result1, result2)
    }
}

Dispatchだと

やっぱり少し複雑

var result1: !
var result2: !
let group = DispatchGroup()
group.enter()
group.enter()
operation1 {
    result1 = $0
    group.leave()
}
operation2 {
    result2 = $0
    group.leave()
}
group.notify(queue: .main) {
    finish(result1, result2)
}

PromiseKit だと

firstly {
    when(fulfilled: operation1(), operation2())
}.done { result1, result2 in
    //…
}

PromiseKit 最高〜〜〜☺️

最後に

Promise Kit とても良いライブラリなのでぜひ使ってみてください!!!
OSS開発に興味が出てきました。
PR出したりして貢献してみたいですね!!!

番外編

RxSwift 公式ドキュメント (RxSwiftできれば解決なんですけどね笑)
RxSwift入門(2) 非同期処理してみる

RxSwift は業務で使っていますが、完璧に理解できているには遠く及ばず。。。
きちんと勉強して、「RxSwift 最高〜〜〜☺️」 となる日が早く訪れるようにします。

77
55
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
77
55

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?