问题
I am trying to fetch data from two different Firebase tables. Here is the structure of table:
Post {
1{
pImages{
i1:true
i2:true
}
}
2{
pImages{
i3:true
}
}
}
Images{
i1{
iUrl : ....
pId : 1
}
i2{
iUrl :...
pId : 1
}
i3{
iUrl:....
pId : 2
}
}
I need to retrieve images corresponding to post with id = 1. The following is my implementation to retrieve images:
func retrieveImagesForPost(postId: String,completion: (result: AnyObject?, error: NSError?)->()){
var imgArray:[Image]=[]
let postsRef = self.ref.child("post")
let imagesRef = self.ref.child("image")
let postImagesRef = postsRef.child(postId).child("pImages");
postImagesRef.observeEventType(FIRDataEventType.Value, withBlock: { (snapshot) in
for item in snapshot.children{
imagesRef.child(item.key).observeSingleEventOfType(.Value, withBlock: { (snap) in
let image = Image(snapshot: snap)
print(image)
imgArray.append(image)
})
}
print(snapshot.key)
print("called")
completion(result:imgArray, error:nil)
})
}
But, the problem is I am not able to get all images in imgArray
to be able to send to completion handler
. Below is the output of calling retrieveImagesForPost
with post id ==1.
pImages
called
<TestProject.Image: 0x7f9551e82000>
<TestProject.Image: 0x7f955466a150>
The images are retrieved after the completion handler
is called. I tried the dispatch groups
and the semaphores
approach as described in the following post. But the results are still the same. How can I make completion handler
to wait for all images to be fetched from Firebase?
回答1:
Keep a counter that you increase as each image is loaded. Once the counter reaches the length of the snapshot.children
list, you're done and call your completion handler.
let postImagesRef = postsRef.child(postId).child("pImages");
postImagesRef.observeEventType(FIRDataEventType.Value, withBlock: { (snapshot) in
var counter = 0
for item in snapshot.children{
imagesRef.child(item.key).observeSingleEventOfType(.Value, withBlock: { (snap) in
let image = Image(snapshot: snap)
print(image)
imgArray.append(image)
counter = counter + 1
if (counter == snapshot.childrenCount) {
completion(result:imgArray, error:nil)
}
})
}
})
You should probably add some error handling in the above, but in general this approach is tried and tested.
回答2:
Another answer for this problem is to use GCD's DispatchGroup
.
First you need to create a dispatch group with DispatchGroup
. In this case, you need to manually tell the group when work is being started with enter()
and when it's finished with leave()
. Then the dispatch group's notify(queue:execute:)
will execute the completion handler on the main queue.
Be careful! The number of enters and leaves must be balanced or the dispatch group's notify will never be called.
let dispatchGroup = DispatchGroup()
let postImagesRef = postsRef.child(postId).child("pImages");
postImagesRef.observeEventType(FIRDataEventType.value, withBlock: { (snapshot) in
for item in snapshot.children{
dispatchGroup.enter()
imagesRef.child(item.key).observeSingleEventOfType(.value, withBlock: { (snap) in
let image = Image(snapshot: snap)
print(image)
imgArray.append(image)
dispatchGroup.leave()
})
}
})
dispatchGroup.notify(queue: DispatchQueue.main, execute: {
completion(result: imgArray)
})
来源:https://stackoverflow.com/questions/38288161/fetch-data-from-firebase-by-joining-tables-in-ios