質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.35%
iOS

iOSとは、Apple製のスマートフォンであるiPhoneやタブレット端末のiPadに搭載しているオペレーションシステム(OS)です。その他にもiPod touch・Apple TVにも搭載されています。

非同期処理

非同期処理とは一部のコードを別々のスレッドで実行させる手法です。アプリケーションのパフォーマンスを向上させる目的でこの手法を用います。

Xcode

Xcodeはソフトウェア開発のための、Appleの統合開発環境です。Mac OSXに付随するかたちで配布されています。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

Q&A

解決済

1回答

6160閲覧

【Swift】処理Aが終わったら処理Bを始めたい

unagimochimochi

総合スコア7

iOS

iOSとは、Apple製のスマートフォンであるiPhoneやタブレット端末のiPadに搭載しているオペレーションシステム(OS)です。その他にもiPod touch・Apple TVにも搭載されています。

非同期処理

非同期処理とは一部のコードを別々のスレッドで実行させる手法です。アプリケーションのパフォーマンスを向上させる目的でこの手法を用います。

Xcode

Xcodeはソフトウェア開発のための、Appleの統合開発環境です。Mac OSXに付随するかたちで配布されています。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

0グッド

0クリップ

投稿2020/10/10 08:10

前提・実現したいこと

A データベース(CloudKit)のレコードから配列を取得
B 配列に要素を追加
C その配列をデータベースに保存

Aが終わったらB、Bが終わったらC、という処理を書きたいです。

発生している問題

以下の関数を実行しますと、
Aに時間がかかるため、取得前にB、その後すぐCが実行されてしまい、
結果として1つのnewPlanIDだけがデータベースに保存されます。

Swift

1let publicDatabase = CKContainer.default().publicCloudDatabase 2 3func addPlanIDToDatabase(accountID: String, newPlanID: String) { 4 5 var planIDsOnDatabase = [String]() 6 7 // ↓---------- A データベースのレコードから配列を取得 ---------- 8 9 let recordID = CKRecord.ID(recordName: "accountID-(accountID)") 10 11 publicDatabase.fetch(withRecordID: recordID, completionHandler: {(record, error) in 12 13 if let error = error { 14 print("予定ID一覧取得エラー: (error)") 15 return 16 } 17 18 if let planIDs = record?.value(forKey: "planIDs") as? [String] { 19 20 for planID in planIDs { 21 planIDsOnDatabase.append(planID) 22 } 23 } else { 24 print("(accountID)のデータベースの予定なし") 25 } 26 }) 27 28 // ↓---------- B 配列に要素を追加 ---------- 29 30 planIDsOnDatabase.append(newPlanID) 31 32 // ↓---------- C その配列をデータベースに保存 ---------- 33 34 let predicate = NSPredicate(format: "accountID == %@", argumentArray: [accountID]) 35 let query = CKQuery(recordType: "Accounts", predicate: predicate) 36 37 publicDatabase.perform(query, inZoneWith: nil, completionHandler: {(records, error) in 38 39 if let error = error { 40 print("(accountID)のデータベースの予定ID追加エラー1: (error)") 41 return 42 } 43 44 for record in records! { 45 46 record["planIDs"] = planIDsOnDatabase as [String] 47 48 self.publicDatabase.save(record, completionHandler: {(record, error) in 49 50 if let error = error { 51 print("(accountID)のデータベースの予定ID追加エラー2: (error)") 52 return 53 } 54 55 print("(accountID)のデータベースの予定ID追加成功") 56 }) 57 } 58 }) 59}

例えばデータベースに"001"があり、そこに"002"を追加して保存したい場合、こんな感じになっています。

理想

A "001"を取得。planIDsOnDatabase = ["001"]
B "002"を追加。planIDsOnDatabase = ["001", "002"]
C planIDsOnDatabase = ["001", "002"]を保存。

現実

A "001"を取得する途中で。planIDsOnDatabase = [String]()
B "002"を追加。planIDsOnDatabase = ["002"]
C planIDsOnDatabase = ["002"]を保存する処理開始。
その後、Aが終わってplanIDsOnDatabase = ["002", "001"]となる。

試したこと

直列処理を書いてみたのですが、知識不足でうまくいきませんでした。

Swift

1let publicDatabase = CKContainer.default().publicCloudDatabase 2 3func addPlanIDToDatabase(accountID: String, newPlanID: String) { 4 5 var planIDsOnDatabase = [String]() 6 7 // ↓---------- A データベースのレコードから配列を取得 ---------- 8 9 let dispatchQueue = DispatchQueue(label: "queue") // 直列キュー 10 11 dispatchQueue.async { 12 13 let recordID = CKRecord.ID(recordName: "accountID-(accountID)") 14 15 self.publicDatabase.fetch(withRecordID: recordID, completionHandler: {(record, error) in 16 17 if let error = error { 18 print("予定ID一覧取得エラー: (error)") 19 return 20 } 21 22 if let planIDs = record?.value(forKey: "planIDs") as? [String] { 23 24 for planID in planIDs { 25 planIDsOnDatabase.append(planID) 26 } 27 } else { 28 print("(accountID)のデータベースの予定なし") 29 } 30 }) 31 } 32 33 // ↓---------- B 配列に要素を追加 ---------- 34 35 dispatchQueue.async { 36 planIDsOnDatabase.append(newPlanID) 37 } 38 39 // ↓---------- C その配列をデータベースに保存 ---------- 40 41 dispatchQueue.async { 42 43 let predicate = NSPredicate(format: "accountID == %@", argumentArray: [accountID]) 44 let query = CKQuery(recordType: "Accounts", predicate: predicate) 45 46 self.publicDatabase.perform(query, inZoneWith: nil, completionHandler: {(records, error) in 47 48 if let error = error { 49 print("(accountID)のデータベースの予定ID追加エラー1: (error)") 50 return 51 } 52 53 for record in records! { 54 55 record["planIDs"] = planIDsOnDatabase as [String] 56 57 self.publicDatabase.save(record, completionHandler: {(record, error) in 58 59 if let error = error { 60 print("(accountID)のデータベースの予定ID追加エラー2: (error)") 61 return 62 } 63 64 print("(accountID)のデータベースの予定ID追加成功") 65 }) 66 } 67 }) 68 } 69}

また、Timerで監視して値が変わったら次の処理を……という記述もしてみたのですが、
処理が重くなる上に邪道と思いますので、できれば正当な方法を望みます。

直列処理、もしくは他の正当な手段を知っている方がいらっしゃいましたら、どうぞよろしくお願いいたします。

補足情報(FW/ツールのバージョンなど)

Xcode 11.6
Swift 5

ここまで読んでくださり、ありがとうございます。
質問に至らぬ点があるかもしれませんが、どうぞよろしくお願いいたします。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

確かに、試されている方法だと直列キューに入ります。

たとえば、Playgorund で以下のような内容を実行すると、キューに入れた順番通りに処理が実施されます。

Swift

1import Foundation 2 3// 直列キューを作る 4let dispatchQueue = DispatchQueue(label: "queue") 5 6// 直列キューに順番に突っ込む 7for i in 1...10 { 8 dispatchQueue.async { 9 print("((i)) 番目の処理") 10 } 11}

しかし、質問者である unagimochimochi さんが試されていることは、「反応時間が確約されていない非同期処理」を直列キューに入れていることが、うまいくいかない原因かと思います。

たとえば、非同期処理を直列キューにいれても、処理時間遅れが一定であれば、処理順番はほぼ同じになります。

Swift

1import Foundation 2 3// 直列キューを作る 4let dispatchQueue = DispatchQueue(label: "queue") 5 6// 直列キューに順番に突っ込む内容が一定時間後の処理の場合 7for i in 1...10 { 8 dispatchQueue.async { 9 dispatchQueue.asyncAfter(deadline: .now() + 1.0) { 10 print("((i)) 番目の処理") 11 } 12 } 13}

ほぼと書いたのは、時間遅れがある非同期処理は、たとえ時間遅れが全て同じ値に設定したとしても、時間遅れが寸分狂わず同一となる保証はないため、上記の処理も数百回試行するともしかしたら順番通りにならないケースもあるかと思います。

一方、今回試されているケースをモデル化すると、下記のようなパターンになるかと思います。
モデルケースですが、もちろん Playground で実行可能です。

Swift

1import Foundation 2 3// 直列キューを作る 4let dispatchQueue = DispatchQueue(label: "queue") 5 6// 直列キューに順番に突っ込む内容に時間揺らぎがある場合 7for i in 1...10 { 8 dispatchQueue.async { 9 // 時間遅れに揺らぎ(0.5秒から1.0秒の間)を与える 10 let delay = Double.random(in: 0.5...1.0) 11 dispatchQueue.asyncAfter(deadline: .now() + delay) { 12 print("((i)) 番目の処理") 13 } 14 } 15}

上記の例を試してみるとわかりますが、直列キューに入れる内容であっても、その処理が非同期であり、処理時間が確約されないケース(たとえばデータベースへのクエリなど)では、キューに入れる順番と処理の結果が同じになるとは限りません。

では、どのようにして解決すれば良いかというと、結局のところは非同期処理を逐次的に処理するよう、自分でコードを書くしかないかと思います。

たとえば、一つひとつの非同期処理をクロージャを受け取る関数に分け、それを逐次的に処理する方法です。

Swfit

1import Foundation 2 3var planIDsOnDatabase = [String]() 4 5// 非同期処理(1) 6func doAsyncProcOne(completion: @escaping () -> ()) { 7 print("非同期処理(1)に入りました") 8 9 // DB への処理を模擬的に行う 10 let delay = Double.random(in: 0.5...1.0) 11 DispatchQueue.main.asyncAfter(deadline: .now() + delay) { 12 print("非同期処理(1)が完了しました。") 13 14 // 文字列を追加する 15 // クラス内でのクロージャ処理の場合には、先頭に self. が必要なので注意 16 planIDsOnDatabase.append("001") 17 18 // 非同期処理完了後の処理を実行する 19 completion() 20 } 21} 22 23// 非同期処理(2) 24// 基本形は doAsyncProcOne と同じ。 25func doAsyncProcTwo(completion: @escaping () -> ()) { 26 print("非同期処理(2)に入りました") 27 28 // DB への処理を模擬的に行う 29 let delay = Double.random(in: 0.5...1.0) 30 DispatchQueue.main.asyncAfter(deadline: .now() + delay) { 31 print("非同期処理(2)が完了しました。") 32 33 // 文字列を追加する 34 // クラス内でのクロージャ処理の場合には、先頭に self. が必要なので注意 35 planIDsOnDatabase.append("003") 36 37 // 非同期処理完了後の処理を実行する 38 completion() 39 } 40 41} 42 43// 実際の処理 44doAsyncProcOne(completion: { 45 // 非同期処理(1)終了後の処理 46 planIDsOnDatabase.append("002") 47 48 // 次の非同期処理をここで呼び 49 doAsyncProcTwo(completion: { 50 print("---終了処理---") 51 // 最後にplanIDsOnDatabase の中身を表示 52 for (index, str) in planIDsOnDatabase.enumerated() { 53 print("[(index)]", str) 54 } 55 }) 56})

最後の「実際の実施」のところで具体的な処理を行いますが、doAsyncProcOne(completion:)で一つ目の処理を行います。

その一つ目の処理が終了した時点で、completion: として渡された処理が行われます。

その中では、次の非同期処理を渡しています。そこでもcompletion: に非同期処理終了後の処理を記述していますので、結果として全ての処理が逐次的に行われるようになっています。

結果として、次のような表示が出るようになっています。

非同期処理(1)に入りました 非同期処理(1)が完了しました。 非同期処理(2)に入りました 非同期処理(2)が完了しました。 ---終了処理--- [0] 001 [1] 002 [2] 003

逐次的に呼び出していますので、planIDsOnDatabaseに追加される文字列は順番通りとなっています。

最初は混乱するかもしれませんが、シンプルなパターンに分解して考え、実験してみると少しは分かりやすくなるのではないでしょうか。

投稿2020/10/10 10:31

編集2020/10/11 05:52
TsukubaDepot

総合スコア5086

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

unagimochimochi

2020/10/11 05:52

いつも丁寧なご回答をありがとうございます。 無事、ひとつ目を終えてから次へ、という処理が実行できました。 直列処理はかかる時間が異なる場合は利用は難しそうですね。 クロージャを使った書き方は今まであまり試していませんでしたので、勉強になりました。 ご回答ありがとうございました!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.35%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問