I am creating an Apple Watch app which has a button that makes a call to the parent iOS app.
WKInterfaceController.openParentApplication(message, reply: {
(response: [NSObject:AnyObject]!, error: NSError!) in
// processing data from iOS app
})
The iOS app responds by retrieving some reminders using EventKit, like so:
eventStore.fetchRemindersMatchingPredicate(predicate, completion: {
[unowned self] reminders in
// sending back data as reply to Apply Watch
})
When the parent app is in the foreground, everything works like a charm. But when I make the app go to the background, something strange happens. The call to EKEventStore, eventStore.fetchRemindersMatchingPredicate(predicate), never calls my completion block. So I never send a reply back to the Apple Watch because of that?
So what is going wrong? I suspect a bug in EventKit, or maybe calling EventKit in the background is not supported. Can anyone shed some light on this?
I figured it out! Before letting EKEventStore spawn an async process, I have to start a background task. When I complete the async call, I have to explicitly end the background task.
// First let iOS know you're starting a background task
let taskIdentifier = application.beginBackgroundTaskWithExpirationHandler() {
() -> Void in
// Do something when task is taking too long
}
// Then do the async call to EKEventStore
eventStore.fetchRemindersMatchingPredicate(predicate, completion: {
[unowned self] reminders in
// Do what I have to do, and afterwards end the background task:
application.endBackgroundTask(taskIdentifier)
})
Related
So I've recently come back to Swift & iOS after a hiatus and I've run into an issue with asynchronous execution. I'm using Giphy's iOS SDK to save myself a lot of work, but their documentation is pretty much nonexistent so I'm not sure what might be happening under the hood in their function that calls their API.
I'm calling my function containing the below code from the constructor of a static object (I don't think that's the problem as I've also tried calling it from a cellForItemAt method for a Collection View).
My issue is that my function is returning and execution continues before the API call is finished. I've tried utilizing DispatchQueue.main.async and removing Dispatch entirely, and DispatchGroups, to no avail. The one thing that worked was a semaphore, but I think I remember reading that it wasn't best practice?
Any tips would be great, I've been stuck on this for waaaaaay too long. Thanks so much in advance
GiphyCore.shared.gifByID(id) { (response, error) in
if let media = response?.data {
DispatchQueue.main.sync {
print(media)
ret = media
}
}
}
return ret
My issue is that my function is returning and execution continues before the API call is finished.
That's the whole point of asynchronous calls. A network call can take an arbitrary amount of time, so it kicks off the request in the background and tells you when it's finished.
Instead of returning a value from your code, take a callback parameter and call it when you know the Giphy call has finished. Or use a promise library. Or the delegate pattern.
The one thing that worked was a semaphore, but I think I remember reading that it wasn't best practice?
Don't do this. It will block your UI until the network call completes. Since you don't know how long that will take, your UI will be unresponsive for an unknown amount of time. Users will think your app has crashed on slow connections.
You could just add this inside a method and use a completion handler and therefore do you not need to wait for the response. You could do it like this:
func functionName(completion: #escaping (YOURDATATYPE) -> Void) {
GiphyCore.shared.gifByID(id) { (response, error) in
if let media = response?.data {
completion(media)
return
}
}
}
Call your method like this
functionName() { response in
DispatchQueue.main.async {
// UPDATE the UI here
}
}
I have been developing an app on iPhone using Swift. I am a newbie and I don't have much experience. I have a problem now. Once I started my app, I used timers for background processes. For instance I was calling functions to check if the response arrived from the server every 0.2 seconds. And that is not the proper way as you know. Now I'm trying to learn treading in swift. What I exactly need is, I need to know when a background process is finished and I need to start another background process.
DispatchQueue.global(cos: .userInteractive).async{
//some request and parsing json
}
Now, when the task is finished I have to start another task. In detail, I will check the last version required, than I will make a login request, than I will pull some images from background, than I will finish the animation. That's why I need to know how to know a background thread is finished so I can start other thread. I think I should use serial queue right?
One more thing, I have to check if process took so much time, so will warn the user about connection.
DispatchQueueHelper.delay(byseconds: x, cos:.background){
// will this work for killing the thread
}
You can use a dispatch group to keep track of your actions:
let dispatchGroup = DispatchGroup()
dispatchGroup.enter()
dispatchGroup.leave()
dispatchGroup.notify(queue: .main) {
// Back on main
}
But this is often when you create multiple requests at once. Normally network requests have their own completion block that will alert you when it's completed. Something like:
URLSession.shared.dataTask(with: url) {(data, response, error) in
// Task is done
}
In your function that you call to execute the request you should leave an completionBlock as:
func execute(request url: URLRequest, completion: (() -> ()) {
URLSession.shared.dataTask(with: url) {(data, response, error) in
completion()
}
}
You of course shouldn't call the completion immediately as you want to handle the data and send it inside.
In swift 5 they introduced Result<Any, Error> which is a great thing to send in your completionBlock completion: ((Result<Model, Error>) -> ())
On the other side it will look something like:
execute(request: request { result in
switch result {
case .success(let model):
// Handle data
case .failure(let error):
// Handle error
}
})
You can use sync instead of async and add multiple tasks in the queue.
This way they will be executed one after another.
On the other hand, async runs them in the same time, and it is more difficult to know which one finished when.
I need to make an API call when the user terminates the app (force close). The straight forward implementation I did is as below.
In the app delegate, I added the following code.
func applicationWillTerminate(_ application: UIApplication) {
print("________TERMINATED___________")
testAPICall()
}
func testAPICall(){
let url = getURL()
let contentHeader = ["Content-Type": "application/json"]
Alamofire.request(url,
method: .put,
parameters: ["username": "abc#xyz.com"],
encoding: JSONEncoding.default,
headers: contentHeader).responseJSON { (response) -> Void in
print("-----")
}
}
However, the call is not being made. And on going through the documentation, I have found that I get only 5 seconds for completing the task in this method and above all, making api call is not a task to be done here. So I wonder, what would be the way to do this.
This is a two fold question
Phase 1: Ensuring API Call starts every time user terminates the app/ before it turns in active
You can always make use of expiration handler background mode of iOS application In your appdelegate
declare
var bgTask: UIBackgroundTaskIdentifier = UIBackgroundTaskIdentifier(rawValue: 0);
and in your appdelegate
func applicationDidEnterBackground(_ application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
bgTask = application.beginBackgroundTask(withName:"MyBackgroundTask", expirationHandler: {() -> Void in
// Do something to stop our background task or the app will be killed
application.endBackgroundTask(self.bgTask)
self.bgTask = UIBackgroundTaskIdentifier.invalid
})
DispatchQueue.global(qos: .background).async {
//make your API call here
}
// Perform your background task here
print("The task has started")
}
Background expiration handler will ensure you will get enough time to start your API call every time you put your application turns inactive or gets terminated
Phase 2: Ensuring API call started finishes successfully
Though expiration handler might ensure that you get enough time to start your API call it can't ensure the successful completion of API call. What if API call takes longer and while the request is in flight and time runs out??
The only way you to ensure that API call gets successful once started is to make sure to use proper configuration for URLSession
As per docs
Background sessions let you perform uploads and downloads of content
in the background while your app isn't running.
link: https://developer.apple.com/documentation/foundation/nsurlsession?language=objc
So make use of Background session and use upload task. Rather than having a plain get/post API which you will hit with some parameter, ask your backend developer to accept a file and put all your param data in that file (if you have any) and start an upload task with background session.
Once the upload task starts with background session iOS will take care of its completion (unless u end up in a authentication challenge obviously) even after your app is killed.
This I believe is the closest you can get to ensure starting a API call and ensuring it finishes once app gets inactive/terminated. I kind a had a discussion with a apple developer regarding the same, and they agreed that this can be a probable solution :)
hope it helps
The main idea here is to make a sync call before app terminate
func applicationWillTerminate(_ application: UIApplication) {
let semaphore: dispatch_semaphore_t = dispatch_semaphore_create(0)
let request = NSMutableURLRequest(URL:url)
let task = NSURLSession.sharedSession().dataTaskWithRequest(request,
completionHandler: {
taskData, _, error -> () in
dispatch_semaphore_signal(semaphore);
})
task.resume()
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
}
Tips:
A dispatch semaphore is an efficient implementation of a traditional counting semaphore. Dispatch semaphores call down to the kernel only when the calling thread needs to be blocked. If the calling semaphore does not need to block, no kernel call is made.
You increment a semaphore count by calling the signal() method, and decrement a semaphore count by calling wait() or one of its variants that specifies a timeout.
here is simple way to achieve this task-
func applicationWillTerminate(_ application: UIApplication) {
let sem = DispatchSemaphore(value: 0)
startSomethingAsync(completionHandler: {
sem.signal()//When task complete then signal will call
})
sem.wait()//waiting until task complete
}
My code in appdelegate for background fetch is never fully run. I have the background fetch option turned on and the plist updated.
I trigger the code by pressing Debug > Simulate Background Fetch
This is the code
func application(application: UIApplication, performFetchWithCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
User.getNotifications(User.getUserDetails()["id"].string!, callback: {(notifications) in
//update notification badge count
notificationBadgeCount = X
})
}
'User.getNotifications' looks like this
getNotifications(id: String, callback...){
alamofire.request(.GET....){ jsonResponse in
//GETS HERE
callback(jsonResponse)
}
}
When triggering the simulated background fetch, the alamofire GET request is sent and data is returned (I've checked the server and the call is sent), however, the app seems to suspend at (//GETS HERE) in the getNotifications call, so the rest the code in the background fetch (//update notification badge count) is never run.
The code seems to time out. I'm supposed to get 30s however it seems to time out in 5s or something.
Any idea why that section of code isn't executed?
NOTE: If I re-open the app manually, then the rest of the code executes.
performFetch has an incoming function called completionHandler. You must call that function to complete the fetch and stop the countdown clock. You are not doing that and you thus are timing out and the app is suspended.
Anyone know how I can show a UIProgressView while I save synchronous to parse.com?
I try to show a progress view before I start the sync save and hide it after the save is done, but this doesn't work. It doesn't show the progress view and just start save right away.
I am starting to think that the sync save takes all the power from everything else and a async save is the best for this issue. But in my case I have to save synchronous since I show the saved data directly after it is saved.
Anyone know how this can be done?
self.startProgress()
self.saveSynchronousToParse()
self.stopProgress()
A 'synchronous' method is also known as a 'blocking' method - it effectively blocks the current thread until it completes.
By default your app is running in the main queue, and this is the queue that performs all of the UI tasks, so once you call "saveSynchronousToParse" (which presumably calls save or some similar synchronous Parse function) your UI will freeze until the task completes. You will probably receive a warning in the console that you are executing a synchronous task on the main thread.
A progress view doesn't really make sense in this case, because you don't get any feedback from the Parse API as to how much progress has been made in saving the information.
A UIActivityView makes more sense. You can use the following to achieve what you are after using an UIActivityView
self.activityView.startAnimating()
self.somePFObject.saveInBackgroundWithBlock {
(success: Bool!, error: NSError!) -> Void in
dispatch_async(dispatch_get_main_queue(),{
self.activityView.stopAnimating()
});
if success {
println("success")
} else {
println("\(error)")
}
}