Fetch data from Firebase by joining tables in iOS

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:

  • IOS Firebase: Loop all key in snapshot.value get messy position
  • FCM Push notifications do not work on iOS 11
  • Getting the values of data stored in firebase
  • Receive limited query with custom start point
  • Firebase Notification Integration on cocoa os x application
  • Firebase ios snapshot returning null for .childRemoved
  •  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?

    2 Solutions Collect From Internet About “Fetch data from Firebase by joining tables in iOS”

    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.

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