FacebookAPIとFirebaseデータベースから画像と情報を取得する複数の非同期リクエストが発生するという問題が発生しました。すべての非同期リクエストを実行してから、Facebook API/Firebaseデータベースから取得したすべてのデータを1つのオブジェクト全体に保存して、すばやく読み込むことができます。非同期リクエストごとに完了ハンドラーを設定しました。リクエストが完了するまでプログラムを「待機」させてからプログラムを続行するように強制しましたが、それはうまくいかないようです。以下は私の試みです:
func setupEvents(completion: (result: Bool, Event: Event) -> Void){
// Get a reference to Events
eventsReference = Firebase(url:"<DB Name>")
eventAttendeesRef = Firebase(url:"<DB Name>")
//Read the data at our posts reference
println("Event References: \(eventsReference)")
eventsReference.observeEventType(FEventType.ChildAdded, withBlock: { (snapshot) -> Void in
let eventName = snapshot.value["eventName"] as? String
let eventLocation = snapshot.value["eventLocation"] as? String
let eventCreator = snapshot.value["eventCreator"] as? String
var attendees: NSMutableDictionary = [:]
var attendeesImages = [UIImage]()
let attendee: NSMutableDictionary = [:]
let group = dispatch_group_create()
//Get attendees first
dispatch_group_enter(group)
self.getAttendees(snapshot.key as String, completion:{ (result, name, objectID) -> Void in
if(result == true){
println("Finished grabbing \(name!) \(objectID!)")
attendees.addEntriesFromDictionary(attendee as [NSObject : AnyObject])
}
else {
println("False")
}
dispatch_group_leave(group)
})
//Get attendees photos
dispatch_group_enter(group)
self.getAttendeesPictures(attendee, completion: { (result, image) -> Void in
if result == true {
println("Finished getting attendee photos. Now to store into Event object.")
attendeesImages.append(image!)
}
else{
println("false")
}
dispatch_group_leave(group)
})
dispatch_group_notify(group, dispatch_get_main_queue()) {
println("both requests done")
//Maintain array snapshot keys
self.eventIDs.append(snapshot.key)
if snapshot != nil {
let event = Event(eventName: eventName, eventLocation:eventLocation, eventPhoto:eventPhoto, fromDate:fromDate, fromTime:fromTime, toDate:toDate, toTime:toTime, attendees: attendees, attendeesImages:attendeesImages, attendeesImagesTest: attendeesImagesTest, privacy:privacy, eventCreator: eventCreator, eventCreatorID: eventCreatorID)
println("Event: \(event)")
completion(result: true, Event: event)
}
}
}) { (error) -> Void in
println(error.description)
}
}
プログラムでテストしたので、完了ハンドラーが正しく設定されていることはわかっています。ただし、必要なのは、getAttendees
関数とgetAttendeesPictures
関数の両方が完了した後でのみ、snapshot
、getAttendees
、およびgetAttendeesPictures
関数を取得したすべての情報を格納し、それらをevent
オブジェクトに格納することです。これを達成する方法についてのアイデアはありますか?このリンクを介してこれを処理するためにdispatch_groups
を調べようとしました: AlamofireとSwiftからの複数の非同期応答をチェックしています しかし、私のプログラムはgetAttendees
関数のみを実行しているようですが、 getAttendeesPictures
関数。以下は、getAttendees
関数とgetAttendeesPictures
関数も示しています。
func getAttendees(child: String, completion: (result: Bool, name: String?, objectID: String?) -> Void){
//Get event attendees of particular event
var attendeesReference = self.eventAttendeesRef.childByAppendingPath(child)
println("Loading event attendees")
//Get all event attendees
attendeesReference.observeEventType(FEventType.ChildAdded, withBlock: { (snapshot) -> Void in
let name = snapshot.value.objectForKey("name") as? String
let objectID = snapshot.value.objectForKey("objectID") as? String
println("Name: \(name) Object ID: \(objectID)")
completion(result: true, name: name, objectID: objectID)
}) { (error) -> Void in
println(error.description)
}
func getAttendeesPictures(attendees: NSMutableDictionary, completion: (result: Bool, image: UIImage?)-> Void){
println("Attendees Count: \(attendees.count)")
for (key, value) in attendees{
let url = NSURL(string: "https://graph.facebook.com/\(key)/picture?type=large")
println("URL: \(url)")
let urlRequest = NSURLRequest(URL: url!)
//Asynchronous request to display image
NSURLConnection.sendAsynchronousRequest(urlRequest, queue: NSOperationQueue.mainQueue()) { (response:NSURLResponse!, data:NSData!, error:NSError!) -> Void in
if error != nil{
println("Error: \(error)")
}
// Display the image
let image = UIImage(data: data)
if(image != nil){
completion(result: true, image: image)
}
}
}
}
タイトルで質問への回答を求めているユーザーの場合、ここで概説されているdispatch_group
とGCDを使用します。つまり、あるグループを別のdispatch_group
の通知メソッドに埋め込むことが有効です。より高いレベルに進む別の方法は、NSOperations
と依存関係であり、これにより、操作のキャンセルなどのさらなる制御も可能になります。
概要:
func doStuffonObjectsProcessAndComplete(arrayOfObjectsToProcess: Array) -> Void){
let firstGroup = dispatch_group_create()
for object in arrayOfObjectsToProcess {
dispatch_group_enter(firstGroup)
doStuffToObject(object, completion:{ (success) in
if(success){
// doing stuff success
}
else {
// doing stuff fail
}
// regardless, we leave the group letting GCD know we finished this bit of work
dispatch_group_leave(firstGroup)
})
}
// called once all code blocks entered into group have left
dispatch_group_notify(firstGroup, dispatch_get_main_queue()) {
let processGroup = dispatch_group_create()
for object in arrayOfObjectsToProcess {
dispatch_group_enter(processGroup)
processObject(object, completion:{ (success) in
if(success){
// processing stuff success
}
else {
// processing stuff fail
}
// regardless, we leave the group letting GCD know we finished this bit of work
dispatch_group_leave(processGroup)
})
}
dispatch_group_notify(processGroup, dispatch_get_main_queue()) {
print("All Done and Processed, so load data now")
}
}
}
この回答の残りの部分はこのコードベースに固有です。
ここにはいくつかの問題があるようです。getAttendees
関数はイベントの子を受け取り、両方とも文字列であるobjectID
とName
を返しますか?このメソッドは参加者の配列を返すべきではありませんか?そうでない場合、返されるobjectID
は何ですか?
一連の参加者が戻ってきたら、グループで処理して写真を取得できます。
getAttendeesPictures
は最終的にFacebookからUIImages
を返します。これらをディスクにキャッシュしてpath ref
を渡すのがおそらく最善です。これらのフェッチされたすべての画像を保持することはメモリに悪影響を及ぼし、サイズと数によってはすぐに問題が発生する可能性があります。
いくつかの例:
func getAttendees(child: String, completion: (result: Bool, attendees: Array?) -> Void){
let newArrayOfAttendees = []()
// Get event attendees of particular event
// process attendees and package into an Array (or Dictionary)
// completion
completion(true, attendees: newArrayOfAttendees)
}
func getAttendeesPictures(attendees: Array, completion: (result: Bool, attendees: Array)-> Void){
println("Attendees Count: \(attendees.count)")
let picturesGroup = dispatch_group_create()
for attendee in attendees{
// for each attendee enter group
dispatch_group_enter(picturesGroup)
let key = attendee.objectID
let url = NSURL(string: "https://graph.facebook.com/\(key)/picture?type=large")
let urlRequest = NSURLRequest(URL: url!)
//Asynchronous request to display image
NSURLConnection.sendAsynchronousRequest(urlRequest, queue: NSOperationQueue.mainQueue()) { (response:NSURLResponse!, data:NSData!, error:NSError!) -> Void in
if error != nil{
println("Error: \(error)")
}
// Display the image
let image = UIImage(data: data)
if(image != nil){
attendee.image = image
}
dispatch_group_leave(picturesGroup)
}
}
dispatch_group_notify(picturesGroup, dispatch_get_main_queue()) {
completion(true, attendees: attendees)
}
}
func setupEvents(completion: (result: Bool, Event: Event) -> Void){
// get event info and then for each event...
getAttendees(child:snapshot.key, completion: { (result, attendeesReturned) in
if result {
self.getAttendeesPictures(attendees: attendeesReturned, completion: { (result, attendees) in
// do something with completed array and attendees
}
}
else {
}
})
}
上記のコードは単なる概要ですが、うまくいけば正しい方向を示しています。
GCDとその周辺のものを使用することで確実に解決策がありますが、一般的に同期は苦痛であり、コードが複雑になるほど、より多くの問題が表示され始めます-しかし、それに対する1対1の解決策があると思います:- ボルトフレームワーク Facebookから(両方ともAndroid na iOS)
ボルトフレームワークの使用法
それで、それについてとても魔法のようなものは何ですか?それでは、「タスク」を作成して、それらをチェーンすることができます。特に関心のあるメソッドはtaskForCompletionOfAllTasks:です。これは、必要なものだけを並列処理するために作成されています。私はあなたがあなたのニーズに合わせることができるあなたのために小さな例を書きました:
func fetchAllInformation() -> BFTask {
// First, create all tasks (if you need more, than just create more, it is as easy as that
var task1 = BFTaskCompletionSource()
var task2 = BFTaskCompletionSource()
var tasks = [task1, task2]
// What you do, is you set result / error to tasks and the propagate in the chain upwards (it is either result, or error)
// You run task 1 in background
API.instance.fetchFirstDetailsInBackgroundWithBlock {
(object: AnyObject!, error: NSError!) -> Void in
// On error or on success, you assign result to task (whatever you want)
if error == nil {
task1.setResult(object)
} else {
task1.setError(error)
}
}
// You run task 2 in background
API.instance.fetchSecondDetailsInBackgroundWithBlock {
(object: AnyObject!, error: NSError!) -> Void in
// On error or on success, you assign result to task (whatever you want)
if error == nil {
task2.setResult(object)
} else {
task2.setError(error)
}
}
// Now you return new task, which will continue ONLY if all the tasks ended
return BFTask(forCompletionOfAllTasks: tasks)
}
Mainメソッドが完了したら、ボルトチェーンマジックを使用できます。
func processFullObject() {
// Once you have main method done, you can use bolts chaining magic
self.fetchAllInformation().continueWithBlock { (task : BFTask!) -> AnyObject! in
// All the information fetched, do something with result and probably with information along the way
self.updateObject()
}
}
Boltsフレームワークのドキュメント/ READMEは、基本的にそれについて知っておくべきことすべてをカバーしており、非常に広範囲にわたるので、それを確認することをお勧めします-一度取得すると非常に使いやすいです基本。私は個人的にこれを正確に使用していますが、これはすばらしいことです。この回答が、さまざまな解決策とアプローチ、おそらくよりクリーンなものを提供することを願っています。
2つのリクエストは同時に実行されているため、2番目のリクエストが実行されたときから写真を取得する参加者はありません。getAttendees
完了クロージャが複数回呼び出される場合は、次のように実行できます。
let group = dispatch_group_create()
for key in keys {
dispatch_group_enter(group)
self.getAttendee(key as String, completion:{ (result, attendee) in
if(result == true){
attendees.addEntriesFromDictionary(attendee)
self.getAttendeesPictures(attendee, completion: { (result, image) in
if result == true {
attendeesImages.append(image!)
}
dispatch_group_leave(group)
})
} else {
dispatch_group_leave(group)
}
})
}
dispatch_group_notify(group, dispatch_get_main_queue()) {}
最初のリクエストの結果が、GCDを使用する必要さえない参加者の完全なセットである場合は、完了クロージャー内でgetAttendeesPictures
を呼び出すだけです。
このコードは、元のコードとまったく同じ変数とメソッドを使用しているわけではなく、アイデアを提供するだけです。
それが役に立てば幸い!
これには概念的に何か問題があります。これらの関数の両方が完了するまで待ってから何か他のことをしたいようですが、説明していないのは、getAttendeesPictures
dependsgetAttendees
の結果について。つまり、本当にやりたいことは、1つの非同期ブロックを実行し、最初の出力で2番目の非同期ブロックを実行し、両方が終了したら最後の完了ブロックを実行することを意味します。
GCDはこれには特に適していません。 NSBlockOperationsでNSOperationQueueを使用することをお勧めします。これには、GCDに比べて2つの明確な利点があります。
NSHipster によるこれの素晴らしい記事があります。これを読んでおくことをお勧めします。これは主に要約で説明されていますが、NSBlockOperationを使用して2つのブロック操作を作成します。1つはgetAttendees
を実行するためのもので、もう1つはgetAttendeesPictures
を実行するためのものです。 2番目のブロックは、両方をキューに追加する前に、最初のブロックに依存します。その後、両方が実行され、2番目の操作で完了ブロックを使用して、両方が完了したら何かを実行できます。
ただし、Dave Robertsは彼の応答に正解です。コードの当面の問題は、getAttendees
関数の出力を使用して実際に参加者を作成しないことです。コードのこの部分が欠落している可能性がありますが、私が見ることができるものから、name
とobjectID
が出力されているだけです。何か便利なものをgetAttendeesPictures
関数に渡したい場合は、最初にこの部分を修正する必要があります。
私が使用したアイデアの1つは、クエリステートメントのコールバック内にifステートメントチェックを配置し、クエリステートメントのコールバックをforループに配置することです(すべてのクエリをループできるようにするため)。したがって、ifステートメントはこれが最後のコールバックが必要な場合は、returnステートメントまたはdeferred.resolveステートメントを実行する必要があります。以下はコンセプトコードです。
var list=fooKeys //list of keys (requests) i want to fetch form firebase
var array=[] // This is the array that will hold the result of all requests
for(i=xyz;loop breaking condition; i++){
Ref = new Firebase("https://yourlink.firebaseio.com/foo/" + fooKeys[i]);
Ref.once("value", function (data) {
array.Push(data.val());
if(loop breaking condition == true){
//This mean that we looped over all items
return array; //or deferred.resolve(array);
}
})
}
このコードを関数に入れて非同期で呼び出すと、結果全体を待ってから他の作業に進むことができます。
あなた(そして他の人たち)がこれが有益だと思うことを願っています。
これは私の頭から離れています。ネストされたブロックがすべて完了した場合にのみ、新しいasycデータを読み取って処理するという考え方です。
Whileループを利用して、シグナルが次のデータセットを読み取るのを待機します。
Doneがfalseに等しい限り、外側のwhileループが続きます。そして、待機中にCPUサイクルを消費する以外は、実際には何も起こっていません。ループ内のifは、すべての参加者が読み取られたときにのみトリガーされます(trueに設定されます)。
一方、ループ内では、ネストされたブロックを処理し、出席者を読み取り、それが完了したら画像を読み取り、完了したらFirebaseデータを読み取ります。最後に、前のブロックのすべてのデータを取得したら、データをオブジェクトに詰め込み、それを辞書に追加します。その時点で、出席者の読み終えたかどうかが判断され、終わった場合は完全に保釈されます。そうでない場合は、次の参加者を読みます。
(これは概念的なものです)
done = false
readyToReadNextAttendee = true
while ( done == false )
{
if (readyToReadNextAttendee == true ) {
readyToReadNextAttendee = false
readAttendee
readPicture
readFirebase {
putDataIntoObject
addObjectToDictionary
if finishedReadingAttendees {
done = true
} else {
readyToReadNextAttendee = true
}
}
}
}
最初にすべての参加者を読み込むオプションがある場合は、readyToReadNextAttendee = trueになるまで次のインデックスを読み込まずに、繰り返して配列することもできます。