Search code examples
iosswiftparse-platformgrand-central-dispatchdispatch

Swift Dispatch Groups with Parse queries


I have 4 functions containing Parse query.findObjectsInBackgroundWithBlock. These are being called to grab data and then populate the table view. Using dispatch groups.

Here is two examples of my parse querys

func getEventImages() {
    print("getEventImages enter")
    dispatch_group_enter(self.group)

    let query = PFQuery(className: "events")
    query.orderByAscending("eventDate")
    query.findObjectsInBackgroundWithBlock { (objects:[AnyObject]!, error: NSError!) -> Void in
        // Initialize your array to contain all nil objects as
        // placeholders for your images
        if error == nil {
            self.eventMainImageArray = [UIImage?](count: objects.count, repeatedValue: nil)
            for i in 0...objects.count - 1 {

                let object: AnyObject = objects[i]
                let mainImage = object["mainImage"] as! PFFile
                //dispatch_group_enter(self.group)
                mainImage.getDataInBackgroundWithBlock({
                    (imageData: NSData!, error: NSError!) -> Void in
                    if (error == nil) {
                            let mainImage = UIImage(data:imageData)
                            self.eventMainImageArray[i] = mainImage
                            print("getEventImages appended")   
                    }
                    else {
                        print("error!!")
                    }
                })
            }
        }
        print("getEventImages leave")
        dispatch_group_leave(self.group)
    }
} 

func getEventInfo() {
    print("eventInfo enter")
    dispatch_group_enter(group)

let query = PFQuery(className: "events")
    query.orderByAscending("eventDate")
    query.findObjectsInBackgroundWithBlock { (objects:[AnyObject]!,error:   NSError!) -> Void in
        self.eventNameArray = [String?](count: objects.count, repeatedValue: nil)
        self.eventInfoArray = [String?](count: objects.count, repeatedValue: nil)
        self.eventDateArray = [NSDate?](count: objects.count, repeatedValue: nil)
        self.eventTicketsArray = [String?](count: objects.count, repeatedValue: nil)

        if error == nil {
            for i in 0...objects.count - 1 {
                let object: AnyObject = objects[i]
                let eventName = object["eventName"] as! String
                let eventInfo = object["eventInfo"] as! String
                let eventDate = object["eventDate"] as! NSDate
                let eventTicket = object["Tickets"] as! String

                self.eventNameArray[i] = eventName
                self.eventInfoArray[i] = eventInfo
                self.eventDateArray[i] = eventDate
                self.eventTicketsArray[i] = eventTicket
                print("event info appended")
            }
        }
        print("event info leave")
        dispatch_group_leave(self.group)
    }
}

And my dispatch_group_nofity

 dispatch_group_notify(group, dispatch_get_main_queue()) { () -> Void in
        print("Finished reloadDataFromServer()")
        self.tableView.reloadData()
        self.refreshControl?.finishingLoading()
    }
}

The problem is that its hit and miss if the data gets retrieved quick enough before dispatch_group_leave(self.group) is called leading to reloading the tableview data too soon. I need to get this so the dispatch_group_leave gets called when the appending is completed.


Solution

  • There is no need for two methods to retrieve the data, no need to unpack the data into multiple arrays and no need to use dispatch groups.

    All you need is a simple method to retrieve your event data

    var events:[PFObject]=[PFObject]()
    
    func getEventInfo() {
        let query = PFQuery(className: "events")
        query.orderByAscending("eventDate")
        query.findObjectsInBackgroundWithBlock { (objects:[AnyObject]!,error:   NSError!) -> Void in
            if error==nil {
                self.events=objects as! [PFObject]
                self.tableView.reloadData()           
            } else {
                print("Something went wrong! - \(error)"
            }
            self.refreshControl?.finishingLoading()
        }
    }
    

    Then, you haven't shown your cellForRowAtIndexPath but you would have something like

    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        var cell = self.tableView.dequeueReusableCellWithIdentifier("cell") as! MyTableViewCell
        let event=self.events[indexPath.row]
    
        cell.eventName.text=event["eventName"] as? String
        cell.eventInfo.text=event["eventInfo"] as? String
        if let mainImageFile=event["mainImage"] as? PFFile {
            mainImageFile.getDataInBackgroundWithBlock({
                (imageData: NSData!, error: NSError!) -> Void in
                if (error == nil) {
                    let mainImage = UIImage(data:imageData)
                    cell.mainImage= mainImage
                }
                else {
                    print("error!!")
                }
        }
        return cell;
    }
    

    You can use a PFImageView or a framework like SDWebImage to handle image caching and putting a placeholder image in place while the image is loaded.

    If you want to update an event is as easy as

     var event=self.events[someindex];
     event["eventName"]=newValue
     event.saveInBackground()