Fetch data from Firebase by joining tables in iOS

2019-03-20 14:07发布

问题:

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)
})