2

I am writing a function that takes a groupchatID (String) and returns a list of Recipients ([String]) for that group chat. I am struggling with the asynchronous part of the function however. When I run the function, it correctly prints to the console the array of usernames I was looking for. Although, when I call the function and try to print the returned value, it is always an empty array because the function returns the array before the firebase call has finished. I am trying to use a callback, but I do not quite understand the syntax of it all. Please take a look and let me know what needs to be changed.

The Function:

func GetRecipientsFor(GroupChatID :String , completion: @escaping ([String]) -> ()) {
var returnArray: [String] = [""]
rootRef.child("chatMembers").child(GroupChatID).observeSingleEvent(of: .value, with: { (snapshot) in
    for child in snapshot.children.allObjects {
        var append = child as! FIRDataSnapshot
        returnArray.append((append.key as String))
        print("Return Array Currently Contains: \(returnArray)")
        //The above printout works properly and when the for loop finishes, the array is exactly as I want it
    }
    completion(returnArray)
    //BUT, this portion returns an empty array
})
}

How I call the function:

GetRecipientsFor(GroupChatID: gchatID) { (result) -> () in
        print(result)
    }

NEW Function Call

var recipients : [String] = [""]
DispatchQueue.main.async {
    GetRecipientsFor(GroupChatID: gchatID) { result in
    print(result) //PRINTS CORRECTLY!!!
    recipients = result
    }
}
print(recipients) //PRINTS A BLANK ARRAY
AL.
  • 36,815
  • 10
  • 142
  • 281
Kyle Papili
  • 419
  • 1
  • 7
  • 16
  • `GetRecipientsFor(GroupChatID: gchatID) { (result) -> () in` to `GetRecipientsFor(GroupChatID: gchatID) { result in` might work? If you print the array the line after `completion(returnArray)` does it print correctly? – Alex Harris May 18 '17 at 14:45
  • Yes, add a print before/after `completion(returnArray)`. There is some other missing detail. This should work as is. – Price Ringo May 18 '17 at 14:50
  • Have you tried putting `var returnArray: [String] = [""]` inside of the closure? – skwashua May 18 '17 at 15:50
  • I changed the syntax I used to call the function as recommended by @chasenyc When I call the function as below, it prints correctly; although, how can I get that resulting value out of the bracket? I am phrasing this poorly, my code will explain it better. ***Please see updated code in question – Kyle Papili May 18 '17 at 21:18
  • Can you edit your initial question with your new code and the problem? – Alex Harris May 18 '17 at 21:20
  • Just updated @chasenyc – Kyle Papili May 18 '17 at 21:22

2 Answers2

3

The problem with

var recipients : [String] = [""]
DispatchQueue.main.async {
    GetRecipientsFor(GroupChatID: gchatID) { result in
    print(result)
    recipients = result
    }
}
print(recipients) // Completes before recipients = result

is that the last line is happening before the async call.

To explain futher print(recipients) happens before recipients = result. All logic using recipients needs to happen within that completion block. All you need to do is

func getRecipients(completion: @escaping ([String]) -> ()) {
    var recipients : [String] = [""]
    DispatchQueue.main.async {
        GetRecipientsFor(GroupChatID: gchatID) { result in
        print(result)
        completion(result)
        }
    }
}

if you want to have further logic included you can call a function within the completion i.e. handleResults(result). I think it would be very beneficial to read more about closures/completion blocks/and async calls.

Paulw11
  • 108,386
  • 14
  • 159
  • 186
Alex Harris
  • 6,172
  • 2
  • 32
  • 57
  • So then how can I delay the 'print(recipients)' line until after the 'recipients = result' line? – Kyle Papili May 18 '17 at 21:26
  • 1
    @KylePapili I've updated my response, let me know if it makes sense. – Alex Harris May 18 '17 at 21:28
  • Ok, I understand what you are suggesting. Currently I do not have a completion funciton. How would I add that to my current function? – Kyle Papili May 18 '17 at 21:29
  • 1
    I've added the function signature. I would suggest reading some about completion handlers to better understand async calls. https://grokswift.com/completion-handlers-in-swift/ – Alex Harris May 18 '17 at 21:33
  • OK, so you are making an entirely new function getRecipients().... I will read up on completion handlers in swift. Thank you for the article, hopefully I'll be able to sort this out. Thank you for all the help! @chasenyc – Kyle Papili May 18 '17 at 21:36
  • 1
    I've also linked to official swift documentation in the answer. Hopefully this helps, if you have more questions about closures, you know where to ask :-) – Alex Harris May 18 '17 at 21:37
0

You also can simplify that and use the firebase observer async task adding other param to your function like this:

//controller is where you need to get the result

    func GetRecipientsFor(GroupChatID :String , controller: UIViewController){
     rootRef.observeSingleEvent(of: .value) { (snapshot) in
            //here you haver your snapshot. do the stuff and 
            controller.setDataForRecipe(dataFromYourSnapshot)
        }
}

And in your controller:

public func setDataForRecipe (arrayIngredients: [String]){
     //whatever you want. example: 
     self.data = arrayIngredients
     self.tableView.reloadData()
}