简体   繁体   中英

Completion Handler - Parse + Swift

I'm trying to generate an array of PFObjects called 'areaList'. I've been researching this quite a bit and understand that I could benefit from using a completion handler to handle the asynchronous nature of the loaded results. My ask, specifically, is to get some guidance on what I'm doing wrong as well as potential tips on how to achieve the result "better".

Here is my query function with completion handler:

    func loadAreasNew(completion: (result: Bool) -> ()) -> [Area] {
        var areaList = self.areaList
        let areaQuery = PFQuery(className: "Area")
        areaQuery.findObjectsInBackgroundWithBlock {
            (areas: [PFObject]?, error: NSError?) -> Void in
            if error == nil {
                for area in areas! {
                    let areaToAdd = area as! Area
                    areaList.append(areaToAdd)
//                    print(areaList)  // this prints the list each time
//                    print(areaToAdd)  // this prints the converted Area in the iteration
//                    print(area)  // this prints the PFObject in the iteration
                    if areaList.count == areas!.count {
                        completion(result: true)
                    } else {
                        completion(result: false)
                    }
                }
            } else {
                print("There was an error")
            }
        }
        return areaList
    }

Here is how I'm attempting to call it in viewDidLoad:

loadAreasNew { (result) -> () in
    if (result == true) {
        print(self.areaList)
    } else {
        print("Didn't Work")
    }
}

I assigned this variable before viewDidLoad:

var areaList = [Area]()

In the console, I get the following:

Didn't Work
Didn't Work
Didn't Work
Didn't Work
[]

Representing the 5 items that I know are there in Parse...

This is an interesting question. First off, PFQuery basically has a built in completion handler, which is quiet nice! As you probably know, all of the code within the areaQuery.findObjectsInBackgroundWithBlock {...} triggers AFTER the server response. A completion most often serves the purpose of creating a block, with the ability of asynchronously returning data and errors.

Best practice would (IMO) to just call the code that you want to use with the results from your PFQuery right after your area appending loop (which I'm gonna take out because I'm picky like that), like so:

     func loadAreasNew() {
        var areaList = self.areaList
        let areaQuery = PFQuery(className: "Area")
        areaQuery.findObjectsInBackgroundWithBlock {
            (areas: [PFObject]?, error: NSError?) -> Void in
            if error == nil {
                let areasFormatted = areas! As [Areas]
                areasList += areasFormatted
                //Something like this
                self.codeINeedAreasFor(areasList)
                }
            } else {
                print(error)
            }
        }
    }

HOWEVER! If you really feel the need to use some completion handlers, check out this other answer for more info on how to use them. But keep in mind all tools have a time and a place...

There are a few issues here.

Your completion handler doesn't require you to define the name for your completion handler's parameters, so you could easily use completion: (Bool) -> ()

Further in your function, you're returning areaList . This should be put through the completion handler like this onComplete(areaList) and change your completion handler parameter to expect your area list.

Then, when you call your function, it could look more like this :

loadAreasNew { result in
    if (result == true) {
        print(self.areaList)
    } else {
        print("Didn't Work")
    }
}

Here is my concern:

1) Don't pass in a local variable and make the function return it, it's meaningless and danger.

You may want to initiate an empty array and make your fetch, then "return" it.

2) The fetch request is processed in background, you will have no idea when it will have finished. If you return the array immediately it will always be an empty array.

Put the "return" in your completion too.

3) Parse already has a distance checking method, you don't have to do it manually. aPARSEQUERRY.where(key:,nearGeoPoint:,inKilometers:)

I will rewrite the function as:

func loadNewAreas(completion:([Area],err?)->()){
    let areaQuery = PFQuery(className: "Area")
    areaQuery.where("location",nearGeoPoint:MYCURRENTLOCATION,inKilometers:50)
    areaQuery.findObjectInBackgroundWithBlock(){objects,err
        if objects.count == 0 
        {
            completion([],err)
        }
        let areas = Area.areasFromPFObjects(objects)
        completion(areas,err)
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM