I'm creating an app that creates a zoom meeting and joins a zoom room into the meeting.
I have got 2 Alamofire requests that I use to call Zoom REST API, mainly 1 to create a zoom meeting and another one to join a zoom meeting, which should be executed when a user clicks the submit button. And the returned result must be stored using a function call. Below is an abstract of the code.
User presses the checkin button
class MyViewController: UIViewController {
var Kiosk = OKKiosk.shared
#IBAction func checkInBtn(_ sender: UIButton) {
userid = FunctiontoGetUserid()
dispatchgroup.enter()
let meetingCredentials = createMeeting(user : userid)
disptachgroup.notify(queue: .main){ () in
joinMeeting(meetingCredentials.meeting_id)
self.Kiosk.currentuser.addReason(meetingCredentials.meeting_id)//doesnt get stored
self.Kiosk.currentuser.addReason(meetingCredentials.start_url)//doesnt get stored
}
Kiosk.currentuser.addReason(meeting_id)//this works
Kiosk.currentuser.addReason(start_url)//But start_url and Meeting id are empty
}
As you can see I need the result from my first call, createMeeting, in my second call joinMeeting.
The problem what I face is whenever I use self.kiosk, nothing gets stored inside the addreason(). But if i directly use kiosk.currentuser.addReason("Example") , it works.
Unfortunately, I'm forced to use self inside dispatchgroup because its closure, Otherwise I get the error :
Reference to property 'Kiosk' in closure requires explicit 'self.' to make capture semantics explicit
I feel the self inside the closure, and the one outside the closure are not pointing to the same Class instance.
Please help me with the above, I'm new to async programming and closures.
Thanks in advance.
Related
I am using Google Sign-In 5.0.2 with a Swift 4 iOS app. Here is my code to get a current id Token:
public static func getJwtToken(completion: #escaping (Result<String, Error>) -> Void) {
assert(Config.isAuthEnabled())
GIDSignIn.sharedInstance()?.currentUser?.authentication.getTokensWithHandler({ gidAuth, error in
if let error = error {
completion(.failure(error))
} else if let token = gidAuth?.idToken {
completion(.success(token))
} else {
assertionFailure("shouldn't have come here")
}
})
}
This works fine when I launch the app.
But if I leave the app running for an hour (to let the id token expire), then the next time getTokensWithHandler is called, it will not call my closure. Subsequent calls to getTokensWithHandler will once again call my closure.
I would like it to behave consistently so that my closure is always called, even if the token needs to be refreshed.
Anyone have any ideas on what I need to do to achieve this?
I figured out the answer to this problem after spending some more time on it.
My problem was I was trying to hack the Google SDK's closure to be synchronous instead of just letting it call the closure when it was ready. The function call that I claimed never called its closure would've called it if I had let the program flow naturally instead of trying to make it call the closure when I expected it to be called. I was using a semaphore somewhere in the call stack that was waiting until the closure got called and I just needed stop making assumptions about when Google's SDK would perform its work.
I'm still trying to get used to this async-first paradigm of Swift.
I'm writing an app in Swift and I've run into a problem I can't find much help for.
So I've got a ViewController and a class named CameraHandler that uses calls the an ActionSheet which then presents camera or gallery picker and saves resulting images.
What I want to do is: call camera, save the image, and then store relevant information to an object.
CameraHandler.shared.showActionSheet(vc: self, reg: car.registration)
self.car.imagePath = CameraHandler.shared.returnFilePath()
self.storeCar() // completes before CameraHandler can get imagepath
My workaround is using a simple DispatchQueue to wait 10 secs hoping it completes in time.
As matt says in his comment, don't wait. Don't expect the answer to be returned from your function. You need to rewrite your showActionSheet() function to take a completion handler. (That is a block of code {more specifically, a closure} that you pass as a parameter.) When your function gets an answer from the async process it needs to complete (fetching a value from a remote server, prompting the user for something, etc.) then it calls the completion handler.
See my answer to the thread below. I include a sample project that uses a function with a completion handler to fetch data from a remote server.
Swift: Wait for Firebase to load before return a function
Using a closure or block in other programing languages as a completion handler, let take an example.
func returnFilePath(completionHandler: ((String) -> ()) {
// after get file path
completionHandler(path)
}
then you can use it like:
returnFilePath { path in
// do some thing with the path
}
This question already has answers here:
Escaping Closures in Swift
(8 answers)
Closed 5 years ago.
I was reading the apple developer documentation's definition of escaping closures. It says "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"
I am not sure what the last part is supposed to mean, what does it mean by "after the function returns"? Does it mean "after the function returns a value"?
Take for example a call to an API. This call is going to take time, but we are going to want to do something after the call is completed. For example, say we want to refresh a UITableView with the new data that we pull. If we were to do it immediately, the data wouldn't have been received yet:
ApiObject.getObjects(completion: { (error, objects) in })
tableView.reloadData()
If we were to reload the data here, the table view will refresh immediately (before we actually have received the data). By doing it in the completion block, we are saying, run the code when we have completed the function, not when the function actually returns:
ApiObject.getObjects(completion: {(error, objects) in
self.tableView.reloadData()
})
Here we are running it once the objects have been fetched, not once the function itself has reached the end.
Edit
Maybe this will make it easier; I have the following code:
let comeInAnimation = POPBasicAnimation(propertyNamed: kPOPLayoutConstraintConstant)!
comeInAnimation.toValue = 0
comeInAnimation.completionBlock = { (anim, success) -> Void in
self.loginButton.enabled = true
self.signupButton.enabled = true
}
signUpContainingViewLeftConstraint.pop_add(comeInAnimation, forKey: AnimationString.EnterExit.identifier)
This is using the POP animation framework. In this case, I have a login and signup button, but I also have an animation for them to appear. I dont want the buttons to be clicked while they are appearing so i have their enabled set to false originally. Now you can see they get set to enabled in the completionBlock. This means, when the animation is completed, the completion block gets called and I know now is the time to set them to be enabled. Had I done it like so:
let comeInAnimation = POPBasicAnimation(propertyNamed: kPOPLayoutConstraintConstant)!
comeInAnimation.toValue = 0
signUpContainingViewLeftConstraint.pop_add(comeInAnimation, forKey: AnimationString.EnterExit.identifier)
self.loginButton.enabled = true
self.signupButton.enabled = true
Even though the enabled properties are set after the animation is called, the properties are actually being set before the animation is complete. Because the program runs line by line, the animation gets added and then immediately the properties are set (this is too early).
Note: This is an issue because these functions run asynchronously. That is, it allows the program to keep running while it is doing its thing. If this line of code blocked (stopped the program until it was compete) then putting the code in a completion block and putting it immediately after would be the same thing. In real life though, we dont want to block because it gives the appearance that the program has frozen.
I'm getting an lldb error whenever I click on a specific tab. The tab is called Account and its supposed to pull data from a child node called "requests" and display that data in a list(table view controller).
I've done this multiple times before. I'm even using my own recycled code for this task, but the error keeps showing up. I've already made sure each button/label is connected to the view controller and I've made sure none are disconnected.
The error message is saying that my request object isn't key-code compliant, but that's not the case either. What else could it be?
This is the code along with some screenshots:
Here is the method I'm using. The error only comes up when I call this function. Other than that, the screen is just blank, so I figured that the issue has to be within the function. btw, requestsArray is an array of requests declared as such: var requestsArray = [requests]()
Heres the rest of the code from that view controller:
EDIT
In Swift 4, the vars need to be marked as #objc like this
class requests: NSObject {
#objc var from: String?
#objc var location: String?
...
}
I’m writing unit tests in swifts, and testing a unique workflow.
In methodA(), I load an object incorrectly (say with incorrect credentials) using an async method. Also kick off an expectation
func methodA(withCred credential: NSURLCredential) {
var objA = ObjectA()
// Set objA.a, objA.b, objA.c,
objA.credential = credential //Incorrect credential First time, Correct Credential second time
objA.delegate = self
expectation = expectationWithDescription(“Aync”)
objA.callAsyncMethod() //This fires successDelegate() or failureDelegate()}
When FailureDelegate() is fired, I reload the object, correctly this time. In order do so, I need to call MethodA() again (so I can reuse all the other stuff there).
func failureDelegate(error: NSError!) {
XCTAssertTrue(error.localizedDescription == “Invalid Credentials“)
//Now that I’ve verified correct error is returned, I need to reload objA
methodA(withCred:correctCredential)
}
func successDelegate(obj : ObjectA) {
XCTAssert(“Object is loaded”)
expectation.fulfill()
}
3.This kicks off the same expectation again in methodA, and results in the following error:
API violation - creating expectations while already in waiting mode.
I understand this is not permitted by swift. Is there a workaround or better way to test these kinds of async methods looping with Swift using XCTest?
Thanks!
Don't share instances of expectation across tests. You should be declaring expectation (i.e. with let) in the body of each test, not as a property on XCTestCase. If you really need to use the delegation pattern (closures would be much, much simpler and more conventional), you can pass that as an additional parameter to your delegate method.
I think your code exemple is incomplete, could you provide the full code?
As #mattt said each test should preferably be unique and should not reuse other test variable.
Regarding your issue, you should declare all your expectation first before the triggering waitForExpectationsWithTimeout:handler:. You can't not create a new expectation after you've start waiting for another one.