I am trying to perform the Finite-Length background task in my app. However, as of now my code is not executed before the app is suspended.
I've followed quite a few tutorials that claims the following to be the way, but obviously I'm getting something wrong. Relevant code should be posted below (please just ask for any clarification if I'm missing something):
class Manager {
private var backgroundTask: UIBackgroundTaskIdentifier = UIBackgroundTaskInvalid
init(){
// Add observer able of detecting when app will go to background
NotificationCenter.default.addObserver(self, selector: #selector(self.didEnterBackground(_:)), name: .UIApplicationDidEnterBackground, object: nil)
}
deinit {
// Observers removed when view controller is dismissed / deallocated
NotificationCenter.default.removeObserver(self)
}
// Routine performed when app will resign from active
#objc private func didEnterBackground(_ notification: Notification){
registerBackgroundTask()
// Code that needs to be executed before app is suspended ------
DispatchQueue.global().sync {
self.isBackgrounding = true
self.shutdownSession()
self.isConnected = false
self.isActivated = false
self.activate = false
self.connectionManager.closeConnectionToPeripherals()
}
// -----------------------------------------------------
self.endBackgroundTask()
}
func registerBackgroundTask() {
backgroundTask = UIApplication.shared.beginBackgroundTask { [weak self] in
self?.endBackgroundTask()
}
assert(backgroundTask != UIBackgroundTaskInvalid)
}
func endBackgroundTask() {
print("\n\n\nBackground task ended.\n\n\n")
UIApplication.shared.endBackgroundTask(backgroundTask)
backgroundTask = UIBackgroundTaskInvalid
}
}
I am facing a problem where the background task always seem to be ended before executing the code. How do I ensure that the code in my synchronous block gets executed before app is suspended?
I don't quite understand the "registerBackgroundTask()" either - even though the internet insists on implementing it this way - as it calls the endBackgroundTask().
You need to move your self.endBackgroundTask() call into your DispatchQueue block, right after self.connectionManager.closeConnectionToPeripherals().
The registerBackgroundTask method does not directly call self.endBackgroundTask(), instead it is only called if the background task expired. Usually, this happens if your app does not complete the task after ~30s in background.
Related
I am trying to clear the pasteboard after a string is copied after 10s. The requirements are the following:
After 10s, the copied text is cleared and therefore not pasteable in
the current app and other apps as well(ex. iMessage, Safari)
If non-identical text is copied, when the 10s is up the timer will not wipe it out
Attempts
I have tried doing this with only DispatchQueue.main.async however, this was freezing the original app.
I have tried doing it with only DispatchQueue.global(qos: .background).async however, when I switched to another app(iMessage), after 10s I could still paste the number. I had to go back to the original app and back to iMessage for it to be wiped out
This is my latest attempt and its the same behavior as #2, only getting wiped out when I go back to the original app and back to iMessage
private func clearTextAfterDelay(_ copiedCardNumber: String) {
expirationTimer?.invalidate()
expirationTimer = Timer.scheduledTimer(withTimeInterval: 10, repeats: false) { timer in
DispatchQueue.main.async {
let currentTextOnClipBoard = UIPasteboard.general.string
if currentTextOnClipBoard == copiedCardNumber {
UIPasteboard.general.setValue("", forPasteboardType: UIPasteboard.Name.general.rawValue)
}
}
}
DispatchQueue.global(qos: .background).async {
let runLoop = RunLoop.current
runLoop.add(self.expirationTimer!, forMode: .default)
runLoop.run()
}
}
Along with this article and the above comment I was able to figure it out https://medium.com/#abhimuralidharan/finite-length-tasks-in-background-ios-swift-60f2db4fa01b. Cheers
class ViewController: MvpViewController {
private var expirationTimerforBackground: Timer?
private var backgroundTask: UIBackgroundTaskIdentifier = UIBackgroundTaskIdentifier.invalid
private func clearTextAfterDelay(_ copiedCardNumber: String) {
backgroundTask = UIApplication.shared.beginBackgroundTask { [weak self] in
self?.endBackgroundTask()
}
assert(backgroundTask != UIBackgroundTaskIdentifier.invalid)
self.expirationTimerforBackground?.invalidate()
self.expirationTimerforBackground = Timer.scheduledTimer(withTimeInterval: 10, repeats: false) { [weak self] _ in
let currentTextOnClipBoard = UIPasteboard.general.string
if currentTextOnClipBoard == copiedCardNumber {
UIPasteboard.general.setValue("", forPasteboardType: UIPasteboard.Name.general.rawValue)
}
self?.endBackgroundTask()
}
}
private func endBackgroundTask() {
UIApplication.shared.endBackgroundTask(backgroundTask)
backgroundTask = UIBackgroundTaskIdentifier.invalid
}
}
Number 2 doesn't work because your app gets suspended almost immediately upon resigning active. So you'd need to extend your app's active time by using background tasks.
Take a look at the beginBackgroundTaskWithExpirationHandler docs.
This method requests additional background execution time for your app. Call this method when leaving a task unfinished might be detrimental to your app’s user experience. For example, call this method before writing data to a file to prevent the system from suspending your app while the operation is in progress.
I have some task which I want to do asynchronously. So I am creating dispatchqueue in one class and doing some task.
//Adding Observer
NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: "NotificationIdentifier"), object: nil, queue: nil, using:catchNotification).
.
//Creating Background Thread.
let anotherQueue = DispatchQueue(label: "Thread1", qos: .background)
//Asynch thread.
anotherQueue.async {
for i in 0..<50 {
print("\n 🔴",i)
sleep(1)
}
}
but upon some events I want to stop this, and am handling something like this.
func catchNotification (notification:Notification) -> Void {
//Stop the queue.
anotherQueue.suspend()
}
Am posting notification some other class. notification is getting received. but thread is not getting suspended.
Here is the complete class if you want to refer.
LoginPresenter.swift
let anotherQueue = DispatchQueue(label: "Thread1", qos: .background)
class LoginPresenter : LoginInteractor {
func catchNotification (notification:Notification) -> Void {
//Stop the queue.
anotherQueue.suspend()
}
func LoginSuccessFull()
{
NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: "NotificationIdentifier"), object: nil, queue: nil, using:catchNotification)
//Asynch thread.
anotherQueue.async {
//Synch
for i in 0..<50 {
print("\n 🔴",i)
sleep(1)
}
}
}
or is any other better method or mechanism is there to do this ?
Reading Apple`s GCD documentation, you could see the following:
The suspension occurs after completion of any blocks running at the time of the call.
This means that, every block will be executed, what has already been started. Just create a second block in your LoginSuccessFull function like:
anotherQueue.async {
print("this is the second block")
}
If you suspend straight after the function call, this block will not be executed.
If you would like to cancel immediately the operations in GCD, you will have to write you own implementation. But to how, it is always the one question for GCD, because it is just not designed that way.
The other option would be to use NSOperationQueue, but there you have implement right the cancel logic for your NSOperation subclasses.
I am developing an iOS app and I want to detect when the user connects/disconnects to Wifi even when the app is closed. I did a lot of research, but still didn't find any solutions to this problem.
Can someone point me in the general direction of how to do this?
It is not possible to detect network connection after the application is closed. The process is shut down and your code cannot be executed.
Check iOS application lifecycle for more details
Maybe you should considered option of application, that can run in background. This is of course possible in iOS, you need Capability type:Background Modes. Then you can check if wifi is availible.
For some unspecified time you can attain this by using Background Fetch.
override init() {
super.init()
initializeBackgroundTask()
NotificationCenter.default.addObserver(self, selector: #selector(networkHasChanged(notification:)), name: NSNotification.Name.reachabilityChanged, object: nil)
}
func networkHasChanged(notification : NSNotification) {
if let reachability = notification.object as? Reachability {
// Do whatever you want to do!!!
}
}
func initializeBackgroundTask() {
if bgTask == UIBackgroundTaskInvalid {
bgTask = UIApplication.shared.beginBackgroundTask(withName: "CheckNetworkStatus", expirationHandler: {
self.endBackgroundTask()
})
}
}
func endBackgroundTask() {
if deepLinkString == nil {
if (self.bgTask != UIBackgroundTaskInvalid) {
UIApplication.shared.endBackgroundTask(self.bgTask)
self.bgTask = UIBackgroundTaskInvalid
}
}
}
deinit {
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.reachabilityChanged, object: nil)
}
Also try not to initialise background task if not in use.
I have a JSON file populated with strings data in Documents Directory. In user Interface of application there is a UIButton. On button press, a new string appends into the JSON file.
Now I am looking for any iOS Service that helps me to send these strings (from JSON file) to the server using swift. And this service should be totally independent of my code.
The point is when I press a UIButton, the first step is a string is saved to the JSON file then service should take this string and send it to server if Internet is available.
When a string sent successfully, it should be removed from the JSON file.
This service should track after every 30 seconds if there is any string saved into JSON file, then send it to server.
I Googled and found background fetch but It triggers performFetchWithCompletionHandler function automatically and I cannot know when iOS triggers it. I want to trigger this kind of service my self after every 30 seconds.
Review the Background Execution portion of Apple's App Programming Guide for iOS.
UIApplication provides an interface to start and end background tasks with UIBackgroundTaskIdentifier.
In the top level of your AppDelegate, create a class-level task identifier:
var backgroundTask = UIBackgroundTaskInvalid
Now, create your task with the operation you wish to complete, and implement the error case where your task did not complete before it expired:
backgroundTask = application.beginBackgroundTaskWithName("MyBackgroundTask") {
// This expirationHandler is called when your task expired
// Cleanup the task here, remove objects from memory, etc
application.endBackgroundTask(self.backgroundTask)
self.backgroundTask = UIBackgroundTaskInvalid
}
// Implement the operation of your task as background task
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
// Begin your upload and clean up JSON
// NSURLSession, AlamoFire, etc
// On completion, end your task
application.endBackgroundTask(self.backgroundTask)
self.backgroundTask = UIBackgroundTaskInvalid
}
What I have done is I just uses the approach discussed by JAL above.
these were the three methods which I used
func reinstateBackgroundTask() {
if updateTimer != nil && (backgroundTask == UIBackgroundTaskInvalid) {
registerBackgroundTask()
}
}
func registerBackgroundTask() {
backgroundTask = UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler {
[unowned self] in
self.endBackgroundTask()
}
assert(backgroundTask != UIBackgroundTaskInvalid)
}
func endBackgroundTask() {
NSLog("Background task ended.")
UIApplication.sharedApplication().endBackgroundTask(backgroundTask)
backgroundTask = UIBackgroundTaskInvalid
}
where updateTimer is of type NSTIMER class
The above functions are in my own created class named "syncService"
This class has an initialiser which is
init(){
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(self.reinstateBackgroundTask), name: UIApplicationDidBecomeActiveNotification, object: nil)
updateTimer = NSTimer.scheduledTimerWithTimeInterval(30.0, target: self, selector: #selector(self.syncAudit), userInfo: nil, repeats: true)
registerBackgroundTask()
}
Then I just called this class and the whole problem is solved.
DispatchQueue.global(qos: .background).async { // sends registration to background queue
}
Please refer NSURLSessionUploadTask might it help you.
Here is the swift 4 version of the answer by JAL
extension UIApplication {
/// Run a block in background after app resigns activity
public func runInBackground(_ closure: #escaping () -> Void, expirationHandler: (() -> Void)? = nil) {
DispatchQueue.main.async {
let taskID: UIBackgroundTaskIdentifier
if let expirationHandler = expirationHandler {
taskID = self.beginBackgroundTask(expirationHandler: expirationHandler)
} else {
taskID = self.beginBackgroundTask(expirationHandler: { })
}
closure()
self.endBackgroundTask(taskID)
}
}
}
Usage Example
UIApplication.shared.runInBackground({
//do task here
}) {
// task after expiration.
}
I want to create an application similar to WhatsApp and Viber (maybe Skype), for iPhone starting from iOS 8.4, using swift 2.2. So I need to send https requests to my server every 30 seconds (I am using `NSURLSession), to show that my application is online and other contacts can see it. There is no problem with it if application is in active state, timer works perfectly and every 30 seconds. But I need application to work also in Inactive, Background, Suspended and Not running (if that even possible) modes, even if iPhone is sleeping. I just need to send little signal, thats all.
I already have this code:
func registerBackgroundTask() {
backgroundTask = UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler {
[unowned self] in self.endBackgroundTask()
}
assert(backgroundTask != UIBackgroundTaskInvalid)
}
func endBackgroundTask() {
UIApplication.sharedApplication().endBackgroundTask(backgroundTask)
backgroundTask = UIBackgroundTaskInvalid
}
func reinstateBackgroundTask() {
if backgroundTask == UIBackgroundTaskInvalid {
registerBackgroundTask()
}
}
func functionToPerformOnBackground() {
requestToServer()
}
This functions are called by timer in AppDelegate.swift like this:
func applicationDidEnterBackground(application: UIApplication) {
//START BACKGROUND TIMER!!!
if !timerForBackgroundRunning {
timerForBackground = NSTimer.scheduledTimerWithTimeInterval(20, target: self, selector: "functionToPerformOnBackground", userInfo: nil, repeats: true)
registerBackgroundTask()
timerForBackgroundRunning = true
}
}
and
func applicationDidBecomeActive(application: UIApplication) {
if timerForBackgroundRunning {
timerForBackground.invalidate()
if backgroundTask != UIBackgroundTaskInvalid {
endBackgroundTask()
}
timerForBackgroundRunning = false
}
}
This code works perfectly, but when iPhone is going to sleep, it stops to send requests. To force code work again I have to go in application again and then press Home Button on iPhone. I have tested Viber and WhatsApp and I can say that Whatsapp is working similar, but Viber knows how to solve this problem.
Can somebody help me, please?
I will be thankful for any help, advice or anything.
P.S. I have already seen this tutorial: http://www.raywenderlich.com/92428/background-modes-ios-swift-tutorial
Is is great but not complete.