Swift closures - order of code execution in regards to value assignment - ios

I would like to know in details the proper order of execution when working on data in a completion handler. I have the following function definition on my Event class:
#nonobjc public class func fetchEvents(completion: #escaping ([Event]) -> () = { _ in }) -> [Event] {
let fetch = self.fetchEventRequest()
var entities: [Event] = []
context.perform {
entities = try! fetch.execute()
completion(entities)
}
return entities
}
And I call the above function from my view controller as such:
events = MyEvents.fetchEvents() { entities in
if (entities.count == 0) {
self.events = Event.getEventsFromAPI()
}
print(events.count) - // returns 0 at this point
self.eventsTableView.reloadData()
}
Upon calling self.eventsTableView.reloadData() my events variable is still empty. I would assume that since the completion handler executes after successful execution of the code inside the function, the events var would have already been assigned the returned value, no?

Here is a summary of your function in question:
...
context.perform {
entities = try! fetch.execute()
completion(entities)
}
return entities
Swift sees it like this:
Call this context.perform function
Call return entities
Process the output of context.perform inside the closure
Because of the order, it just returns the value and considers its work done. Your closure will likely complete, assuming it stays in scope, but otherwise nothing comes of it.
The easiest thing would be to change your function from:
#nonobjc public class func fetchEvents(completion: #escaping ([Event]) -> () = { _ in }) -> [Event]
to:
#nonobjc public class func fetchEvents(completion: #escaping ([Event]) -> () = { _ in })
The difference here is the removal of the [Event] return.
Then, inside your function remove the return entities line.
You now have an asynchronous function, so whatever you use to call it will have to use a closure for the [Event] value.
self.events = Event.getEventsFromAPI() becomes something like:
Event.getEventsFromAPI() = { [weak self] events in
self?.events = events
}

Related

How to call a method which takes a closure as a parameter in Swfit?

I try to write a method as below:
class ThisIsExample{
func theMethod(inside:((Error?)->Void)->Void){
//some implementation
}
}
But, when I try to call this method, I don't know how to do that.
I wrote code below:
let example = ThisIsExample()
example.theMethod { ({(err) in }) in
print("print something")
}
I try to write another closure, which is { (err) in } inside the closure
But it is not workable, I'll receive error message like
Contextual closure type '((Error?) -> Void) -> Void' expects 1
argument, but 0 were used in closure body
So...could anyone please teach me how to call this method in correct way, thank you so much.
Although not sure what is the purpose of nested closure. But if you want to use this approach then you should call the closure in this way,
example.theMethod { (closure) in
closure(NSError.init())
}
You can do some thing like this:
func theMethod(inside:(Error?) -> ()) {
print("Closure as paramater")
}
This will take Error as closure parameter and return void.
you can call this function as below:
theMethod { (error) in
}
Something Like This
class NewClass
{
class func myCustomFunc(_ parameter1: String, parameterDict : [String : AnyObject]?, success:#escaping (String) -> Void, failure:#escaping (String) -> Void)
{
/// Just example Bool
var result : Bool = false
/// Get the parameter you sent while calling this Clousure block
let myParamString = parameter1
let paramDict = parameterDict
/// Share the output in case of Success and Failure
if (result){
success("success")
}
else{
failure("Failure")
}
}
}
Usage
NewClass.myCustomFunc("demoStr", parameterDict: [:], success: { (succesString) in
if succesString == "success"{
}
}) { (failureStr) in
if failureStr == "failure"{
}
}
This Function Accepts Parameter and Also give Different blocks depend upon closures

Swift 4 accessing completionhandler returned value from class variable

Problem: I’m trying to access the value which is returned from completionHandler, to assign it to a variable which is outside the scope of completionHandler returned method. I can access the variable in the scope of the method, but I can’t from outside. I’ve tried to assign it to the class variable when I access, but didn’t work. Any ideas?
var marketler = [MarketModel]()
var marketAdiArray = [String]()
override func viewDidLoad() {
getMarkets { (marketdizi) in
self.objAryToTableView(markets: marketdizi)
print(self.marketAdiArray) // -> this returns the right array
}
print(self.marketAdiArray) // -> this returns an empty array
}
func getMarkets(completionHandler : #escaping ([MarketModel])->()) {
let uid = "userID(02)"
print("uid : \(uid)")
MobileUserViewModel().getUser(userId: uid, completionHandler: { (user) in
// here returns an user object
self.loginUser = user
MarketViewModel().getMarketFromDb(mobilUser: user, completionHandler: { (marketler) in
print("marketler : \(marketler)")
completionHandler(marketler)
})
})
}
func objAryToTableView(markets : [MarketModel]) {
var ary = [String]()
for m in markets {
ary.append(m.marketName as String!)
}
self.marketAdiArray = ary
}
The code inside the block (completion handler) is getting executed after getMarketFromDb call succeeds. The code which is outside the block is executed just after the previous line where you don't have any data on the array.
If You need to trigger the UI update which updated data from data Db then you need to invoke the UI update from inside the completion block.
getMarkets { (marketdizi) in
self.objAryToTableView(markets: marketdizi)
print(self.marketAdiArray) // -> this returns the right array
self.tableView.reloadData()
}
print(self.marketAdiArray) // -> this returns an empty array

Swift - How to properly remove blocks from an array when a caller is deallocated?

I have an array of 'updateBlocks' (closures) that I use in a singleton class to notify any observers (UIViewControllers, etc) when data updates.
I am wondering what the best way to remove the observer would be so that it is not executed when the observer is deallocated (or no longer wants updates).
Here is my current setup:
MySingleton Class
var updateBlock: (() -> ())? {
didSet {
self.updateBlocks.append(updateBlock!)
self.updateBlock!() // Call immediately to give initial data
}
}
var updateBlocks = [() -> ()]()
func executeUpdateBlocks() {
for block in updateBlocks {
block()
}
}
MyObserver Class
MySingleton.shared.updateBlock = {
...handle updated data...
}
MySingleton.shared.updateBlock = nil // How to properly remove???
Your singleton design has some problems.
Having updateBlock be a variable who's didSet method appends a block to your updateBlocks array is bad design.
I would suggest getting rid of the updateBlock var, and instead defining an addUpdateBlock method and a removeAllUpdateBlocks method:
func addUpdateBlock(_ block () -> ()) {
updateBlocks.append(block)
}
func removeAllUpdateBlocks() {
updateBlocks.removeAll()
}
func executeUpdateBlocks() {
for block in updateBlocks {
block()
}
}
If you want to remove single blocks then you'll need some way to keep track of them. As rmaddy says, you would need some sort of ID for each block. You could refactor your container for your blocks to be a dictionary and use sequential integer keys. When you add a new block, your addBlock function could return the key:
var updateBlocks = [Int: () -> ()]()
var nextBlockID: Int = 0
func addUpdateBlock(_ block () -> ()) -> Int {
updateBlocks[nextBlockID] = block
let result = nextBlockID
nextBlockID += 1
//Return the block ID of the newly added block
return result
}
func removeAllUpdateBlocks() {
updateBlocks.removeAll()
}
func removeBlock(id: Int) -> Bool {
if updateBlocks[id] == nil {
return false
} else {
updateBlocks[id] = nil
return true
}
func executeUpdateBlocks() {
for (_, block) in updateBlocks {
block()
}
If you save your blocks in a dictionary then they won't be executed in any defined order.
That's a very confusing API. From the client's point of view you are setting the value of a single block. But the implementation actually adds that block to an array and then immediately calls that block. And why would you force-unwrap the optional block?
Since you want to support several observers and provide the ability to remove observers, you really show have addBlock and removeBlock methods in your singleton. Then the API and its functionality are clear.
The trick is how to provide an API that lets an observer tell the singleton to remove a specific block. I would model the API after how it is done in the NotificationCenter class where the addBlock method returns some generated token. That token is then passed to the removeBlock method.
The implementation would likely be a dictionary keyed on the token and the value is the block. The token can be a UUID or some other generated, unique opaque value. That makes the addBlock and removeBlock methods simple. Then the executeBlocks method would iterate the values of the dictionary and call those blocks.
Here's one possible implementation:
class UpdateBlocks {
static let shared = UpdateBlocks()
var blocks = [UUID: () -> ()]()
private init() {
}
func addBlock(_ block: #escaping () -> ()) -> Any {
let token = UUID()
blocks[token] = block
return token
}
func removeBlock(_ token: Any) {
if let token = token as? UUID {
blocks[token] = nil
}
}
func executeBlocks() {
for (_, value) in blocks {
value()
}
}
}
let token = UpdateBlocks.shared.addBlock {
print("hello")
}
UpdateBlocks.shared.executeBlocks() // Outputs "hello"
UpdateBlocks.shared.removeBlock(token)
UpdateBlocks.shared.executeBlocks() // No output

Swift Function returning a value from asynchronous firebase call

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

Swift completion handler for Alamofire seemingly not executing

I have the following function in a class in my program:
func getXMLForTrips(atStop: String, forRoute: Int, completionHandler: #escaping (String) -> Void) {
let params = [api key, forRoute, atStop]
Alamofire.request(apiURL, parameters: params).responseString { response in
if let xmlData = response.result.value {
completionHandler(xmlData)
} else {
completionHandler("Error")
}
}
}
In the init() for the class, I attempt to call it like this:
getXMLForTrips(atStop: stop, forRoute: route) { xmlData in
self.XMLString = xmlData
}
This compiles without errors, but after init() is executed, my class's self.XMLString is still nil (shown both by the Xcode debugger and by my program crashing due to the nil value later on). I see no reason why this shouldn't work. Can anyone see what I am missing?
You shouldn't be making internet calls in the initializer of a class. You will reach the return of the init method before you go into the completion of your internet call, which means it is possible that the class will be initialized with a nil value for the variable you are trying to set.
Preferably, you would have another class such as an API Client or Data Source or View Controller with those methods in it. I am not sure what your class with the init() method is called, but lets say it is called Trips.
class Trips: NSObject {
var xmlString: String
init(withString xml: String) {
xmlString = xml
}
}
Then one option is to put the other code in whatever class you are referencing this object in.
I'm gonna use a view controller as an example because I don't really know what you are working with since you only showed two methods.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
//setting some fake variables as an example
let stop = "Stop"
let route = 3
//just going to put that method call here for now
getXMLForTrips(atStop: stop, forRoute: route) { xmlData in
//initialize Trip object with our response string
let trip = Trip(withString: xmlData)
}
}
func getXMLForTrips(atStop: String, forRoute: Int, completionHandler: #escaping (String) -> Void) {
let params = [api key, forRoute, atStop]
Alamofire.request(apiURL, parameters: params).responseString { response in
if let xmlData = response.result.value {
completionHandler(xmlData)
} else {
completionHandler("Error")
}
}
}
}
If you want to be able to initialize the class without requiring setting the xmlString variable, you can still do the same thing.
Change the Trips class init() method to whatever you need it to be and set var xmlString = "" or make it optional: var xmlString: String?.
Initialize the class wherever you need it initialized, then in the completion of getXMLForTrips, do trip.xmlString = xmlData.

Resources