I have a function right now that is determining the hide behavior of a UILabel: func shouldHideLabel() -> Bool.
I need to retrieve data from a web request to determine whether or not to hide this label, so inside of shouldHideLabel() I'm making a call to a function func webRequestDataIsValid() -> Bool.
webRequestDataIsValid() is doing a web request with a closure, returning an object, and the object has a variable isValid on it, which is returning true or false.
My goal is to wait on this isValid flag to be returned to me, return from webRequestDataIsValid with true or false, and then use that return value to return from the original shouldHideLabel function.
I'm using a completion handler inside of shouldHideLabel to wait on the data from webRequestDataIsValid, but I'm not sure how to wait on returning inside of shouldHideLabel for my closure to finish. It seems like you can't return from a function while inside of a closure.
Any help is much appreciated. Thank you!
I would assume that your web request will contain a completionBlock,
try this:
func webRequestDataIsValid(completion: #escaping ((Bool) ->Void)) {
whateverYourRequestMethod.response { httpResponse in
let isValid = true // retrieve your boolean
completion(isValid)
)
}
Related
I've done some research on closures, closures that receive parameters, trailing closures, and completion handlers, but I'm having difficulty understanding when the two arguments to completion, manager and file are passed. In the pick() function definition, there is nowhere in the function scope that calls the completion with completion(manager, file) syntax. There is, however, a present method and I think I am missing something about present() that may include the call for the completion closure with proper arguments. I would appreciate your help.
public func pick(from vc: UIViewController?, withCompletion completion: #escaping (_ manager: HSDriveManager?, _ file: GTLRDrive_File?) -> Void) {
viewer?.completion = completion
viewer?.shouldSignInOnAppear = true
//As of now, present() seems to include the calling of the completion closure.
//self is the HSDriverPicker class
print(type(of: self))
vc?.present(self, animated: true)
}
The code below is the function call for pick. What confuses me is that in order for manager and file to act like arguments of the completion closure somewhere in the function call there has be a part that passes those arguments. I don't see them. I would appreciate your insight on what exactly the present method does and whether it takes care of calling the closure with necessary arguments.
picker.pick(from: self) {
(manager, file) in
print("picked file: \(file?.name ?? "-none-")")
let destinationPath = "/Users/james/Desktop/tests"
manager!.downloadFile(file!, toPath: destinationPath, withCompletionHandler: {
error in
if error != nil {
print("Error downloading : \(error?.localizedDescription ?? "")")
}
else {
print("Success downloading to : \(destinationPath)")
}
})
}
The function present(_:animated:) won't be responsible for calling the completion closure. viewer?.completion = completion means viewer will take care of calling the completion closure in its scope(that's why #escaping is used, i.e the closure will outlive the scope that you've passed it to). The two arguments manager and file aren't supposed to be passed by the client but are exposed to the client so that whoever calls the picker.pick could use these properties and perform the certain action that will then called inside viewer. The closure with parameters simply means the client could use that parameter without worrying about who and when will those parameters be passed.
I have a predefined function that has a completion param:
func checkNotificationEnabled(_ resultBlock : ((Bool)->())? = nil){
Bool enabled = false
... a big block of code that gets enabled value
...
... end block
resultBlock?(enabled)
}
I need to get the true/false and pass it to another function:
#objc
func isNotificationEnabled(_
resolve: RCTPromiseResolveBlock, rejecter reject: RCTPromiseRejectBlock
) -> Void {
checkNotificationEnabled { (enabled:Bool) in
resolve(enabled)
}
}
Got the error: Escaping closure captures non-escaping parameter 'resolve'
How can I pass enabled to resolve ?
Presuming RCTPromiseResolveBlock on func isNotificationEnabled to be some block that you're trying to execute with respect to completion of func checkNotificationEnabled, the completion actually 'escapes' (outlives) the scope of the function, and the compiler simply complains that your RCTPromiseResolveBlock may not be alive (in execution) during the completion callback
You'll have to mark #escaping to the 'resolve' parameter to get around this.
You'll definitely get more clarity when you understand what escaping closures are. Have a look at this question: Escaping Closures in Swift
Hope this helps.
resolve argument is passed to a function checkNotificationEnabled but is not marked #escaping
(Bool)->())? = nil optional is nil by default, so no need to assign nil
(Bool)->() this is equal to more readable version Void.
typealias ResultBlock = (Bool) -> Void
func checkNotificationEnabled(_ resultBlock: ResultBlock?) {
var enabled = false
... a big block of code that gets enabled value
...
... end block
resultBlock?(enabled)
}
#objc
func isNotificationEnabled(_
resolve: #escaping RCTPromiseResolveBlock, rejecter reject: RCTPromiseRejectBlock) {
checkNotificationEnabled { enabled in
resolve(enabled)
}
}
Escaping closures
A closure is said to escape a function when the closure is passed as
an argument to the function, but is called after the function returns.
When you declare a function that takes a closure as one of its
parameters, you can write #escaping before the parameter’s type to
indicate that the closure is allowed to escape.
One way that a closure can escape is by being stored in a variable
that is defined outside the function. As an example, many functions
that start an asynchronous operation take a closure argument as a
completion handler. The function returns after it starts the
operation, but the closure isn’t called until the operation is
completed—the closure needs to escape, to be called later.
source
I wrote a function which takes a closure as an argument like this:
func doSome(work: () -> Void = { print("sleeping...") } ) {
work()
}
Now I would like to investigate the work done.
Therefore I want to check if the given closure contains any print statements.
Somehow like this:
func doSome(work: () -> Void = { print("doing hard work...") } ) {
work()
if work.contains(print) {
print("we did some hard work there and printed something!")
}
}
How can I achieve that?
EDIT: What I am trying to achieve
An async function tries to connect to an http server - let's call it connect. It takes a closure as its parameter - called finally. As its name already indicates: the closure gets executed after the connecting attempt.
If the connecting attempt succeeds (http response code == 200), I need to call another function ONCE - let's call it so: once.
The connect function therefore looks like this:
func connect(finally: () -> Void = {}) {
httpRepsonse = asyncRequestToServer()
if httpResponse.statusCode == 200 {
once()
}
// and finally:
finally()
}
Other functions call connect and pass over their statements that they need for the connect function to execute finally.
And here comes the problem: there is one function that needs once executed every time, therefore it passes it over in the finally closure. If the connecting now succeeds, once gets called twice.
That's why I wanted to check the given closure already contains the once call, so I could avoid calling it twice.
Interrogating a closure for its contents is not easily done as far as I know.
You could do a workaround (depending on your needs and implementation of course) using one or more Boolean arguments which you would assign when calling the function, if relevant.
For example:
func doSome(work: () -> Void = { print("doing hard work...")}, containsPrint: Bool = false) {
// Call your work closure
work()
// Check conditions
if containsPrint {
print("We printed some stuff")
}
}
I am aware that this is a rather simple solution but it might provide the required functionality.
Use a global variable that you change whenever you print to the console, and check it inside you doSome(work:)
Short answer: You can't. As Alexander says, Swift does not support this. You would have to add some sort of housekeeping, as suggested in Carpsen90's answer.
I am trying to add a variable in the String class, of type Bool, that if used on a userName as String, is supposed to check Firebase and return true if the username exists and false if not. In the end I am aiming to use it like this:
username.isUserNameAvailable
The problem is that my Firebase function returns data asynchronously. Therefore in my function below, variable isTaken, is assigned a value after variable availability is returned from the function. Is there a way to fix this?
fileprivate extension String {
var isUserNameAvailable : Bool {
var availability : Bool?
DatabaseManager.system.isUserNameTaken(userName: self, completion: {(isTaken) in
availability = !isTaken
print("isTaken = \(isTaken)")
})
print("availability = \(availability)")
return availability!
}
}
You shouldn't try to treat an asynchronous call as something that is already available.
I would suggest having a method that calls a code block when the Firebase request is finished.
Something like this:
fileprivate extension String {
func isUserNameAvailable(completion: (Bool)->()) {
DatabaseManager.system.isUserNameTaken(userName: self, completion:{(isTaken) in
completion(!isTaken)
})
}
}
You would call it like this:
string.isUserNameAvailable { (available) in
//use the variable
}
It's not exactly what you wanted, but it still simplifies the call while showing a bit more clearly that the call is asynchronous.
I have a function that does some work and call completion. Something like this
func doStuff(completion: (Bool) -> ()) {
performWork()
completion(true)
}
The problem is that performWork triggers some process that receives result in other method. And depending on this result I need call completion with success or not based on data from previous method.
Is there any possible solution ? Method doStuff can not be modified and I don't have access to performWork() its third party, I can only call it.
You should save completion in a variable in the scope of your class and execute it in the delegate method of your API.
var doStuffCompletion: (Bool) -> ()!
func doStuff(completion: (Bool) -> ()) {
performWork()
doStuffCompletion = completion
}
func apiStuffFinished(success: Bool) {
doStuffCompletion(success)
}