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.
Related
We have a web-view which hosts a web action sheet with a timer. Whenever there is a navigation to a different app and back to our app, the timer gets stuck and the web action sheet becomes unresponsive.
Any solutions or work around to solve this issue ?
Use the following function to create a timer that keeps working when the app is in background:
func executeAfterDelay(delay: TimeInterval, completion: #escaping(()->Void)){
backgroundTaskId = UIApplication.shared.beginBackgroundTask(
withName: "BackgroundSound",
expirationHandler: {[weak self] in
if let taskId = self?.backgroundTaskId{
UIApplication.shared.endBackgroundTask(taskId)
}
})
let startTime = Date()
DispatchQueue.global(qos: .background).async {
while Date().timeIntervalSince(startTime) < delay{
Thread.sleep(forTimeInterval: 0.01)
}
DispatchQueue.main.async {[weak self] in
completion()
if let taskId = self?.backgroundTaskId{
UIApplication.shared.endBackgroundTask(taskId)
}
}
}
}
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.
The memory of my app (navigation app) keeps on growing until iOS kills it sooner or later. I was able to pinpoint the problem to the use of a custom DispatchQueue in a timer target function that fires every second. (i.e. while navigating a timer is running that fires every second and executes the updateUI function below.)
This is an extract of the viewcontroller:
class NavViewController: UIViewController, ... {
...
let processLocationUpdateQeue = DispatchQueue(label: "processLocationUpdateQeue", qos: .userInteractive)
var updateUITimer: Timer!
...
func startNav() {
...
DispatchQueue.main.async { [unowned self] (_) -> Void in
self.updateUITimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(NavViewController.updateUI), userInfo: nil, repeats: true)
}
...
}
func updateUI() {
let freeDrive = self.isFreeDrive ?? false
processLocationUpdateQeue.async { [unowned self] in
self.totalBreakTimeInclCurrentBreak = self.totalBreakTimeInSec
if !self.takingBreak {
if self.timeOfLastPositionUpdate != nil && self.timeOfLastPositionUpdate.secondsAgo() >= 90 {
// some code that's not being executed during my analyses of this problem
}
}
else {
self.totalBreakTimeInclCurrentBreak = self.totalBreakTimeInSec + Float((self.breakStartTime ?? Date()).secondsAgo())
if ((self.autoResumeSwitch != nil && self.autoResumeSwitch!.isOn) || self.autoResumeSwitch == nil) && self.breakStartCoordinate?.distance(from: lastKnownLocation.coordinate.toCLLocation()) > 100 {
// some code that's not being executed during my analyses of this problem
}
}
self.avgSpeedMperS = min(self.maxSpeedMperS,self.coveredDistanceInMeters/(Float(self.tripStartTime.secondsAgo())-self.totalBreakTimeInclCurrentBreak))
if freeDrive && UIApplication.shared.applicationState == .active {
// app is running in background for my analyses, so the code here doesn't get executed
}
}
}
}
When I run my app using Instruments allocations tool I see the memory growing. Most (in fact all) of the growth is in non-object and the stack trace points to the updateUI function. See this screenshot:
Note the trace step just after (above) the step where the function is called. It's something with DispatchQueue.
I've been analysing this for 2 days now, tried a bunch of things. Here's what I know at this point:
Memory does not grow if I take the code out of processLocationUpdateQeue.async {} and run it on the main queue.
Wrapping the code inside processLocationUpdateQeue.async {} into autoreleasepool {} doesn't help
Using processLocationUpdateQeue = DispatchQueue(label: "processLocationUpdateQeue", qos: .userInteractive, attributes: [], autoreleaseFrequency: .workItem, target: nil) (note the .workItem) doesn't help.
I'm using [unowned self] so retain cycle is not the problem (I think)??
I tried using [weak self], same problem.
I tried DispatchQueue(label: "processLocationUpdateQeue", qos: .userInteractive).async {} instead of using the class let processLocationUpdateQeue, same problem
Simulating a memory warning doesn't 'clean' the growth
The memory graph debugger doesn't show any leaks
What am I doing wrong?
UPDATE
So, this is interesting. I only see this when my app is running in the background. Also, when I bring my app to the foreground the related non-objects that were built up while backgrounded, disappear !!!
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 am using Alamofire to download data
How to make alamofire run download in background with swift?
Thanks
The basic idea is as follows:
The key problem is that with background downloads, your app may actually be terminated while downloads are in progress (e.g. jettisoned due to memory pressure). Fortunately, your app is fired up again when background downloads are done, but any task-level closures you originally supplied are long gone. To get around this, when using background sessions, one should rely upon session-level closures used by the delegate methods.
import UIKit
import Alamofire
import UserNotifications
fileprivate let backgroundIdentifier = ...
fileprivate let notificationIdentifier = ...
final class BackgroundSession {
/// Shared singleton instance of BackgroundSession
static let shared = BackgroundSession()
/// AlamoFire `SessionManager`
///
/// This is `private` to keep this app loosely coupled with Alamofire.
private let manager: SessionManager
/// Save background completion handler, supplied by app delegate
func saveBackgroundCompletionHandler(_ backgroundCompletionHandler: #escaping () -> Void) {
manager.backgroundCompletionHandler = backgroundCompletionHandler
}
/// Initialize background session
///
/// This is `private` to avoid accidentally instantiating separate instance of this singleton object.
private init() {
let configuration = URLSessionConfiguration.background(withIdentifier: backgroundIdentifier)
manager = SessionManager(configuration: configuration)
// specify what to do when download is done
manager.delegate.downloadTaskDidFinishDownloadingToURL = { _, task, location in
do {
let destination = try FileManager.default.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
.appendingPathComponent(task.originalRequest!.url!.lastPathComponent)
try FileManager.default.moveItem(at: location, to: destination)
} catch {
print("\(error)")
}
}
// specify what to do when background session finishes; i.e. make sure to call saved completion handler
// if you don't implement this, it will call the saved `backgroundCompletionHandler` for you
manager.delegate.sessionDidFinishEventsForBackgroundURLSession = { [weak self] _ in
self?.manager.backgroundCompletionHandler?()
self?.manager.backgroundCompletionHandler = nil
// if you want, tell the user that the downloads are done
let content = UNMutableNotificationContent()
content.title = "All downloads done"
content.body = "Whoo, hoo!"
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 1, repeats: false)
let notification = UNNotificationRequest(identifier: notificationIdentifier, content: content, trigger: trigger)
UNUserNotificationCenter.current().add(notification)
}
// specify what to do upon error
manager.delegate.taskDidComplete = { _, task, error in
let filename = task.originalRequest!.url!.lastPathComponent
if let error = error {
print("\(filename) error: \(error)")
} else {
print("\(filename) done!")
}
// I might want to post some event to `NotificationCenter`
// so app UI can be updated, if it's in foreground
}
}
func download(_ url: URL) {
manager.download(url)
}
}
Then I can just initiate those downloads. Note, I do not specify any task-specific closure when I initiate the download, but rather merely use the above session-level closures that use the details of the URLSessionTask to identify what to do:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// request permission to post notification if download finishes while this is running in background
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound]) { granted, error in
if let error = error, !granted {
print("\(error)")
}
}
}
#IBAction func didTapButton(_ sender: Any) {
let urlStrings = [
"http://spaceflight.nasa.gov/gallery/images/apollo/apollo17/hires/s72-55482.jpg",
"http://spaceflight.nasa.gov/gallery/images/apollo/apollo10/hires/as10-34-5162.jpg",
"http://spaceflight.nasa.gov/gallery/images/apollo-soyuz/apollo-soyuz/hires/s75-33375.jpg",
"http://spaceflight.nasa.gov/gallery/images/apollo/apollo17/hires/as17-134-20380.jpg",
"http://spaceflight.nasa.gov/gallery/images/apollo/apollo17/hires/as17-140-21497.jpg",
"http://spaceflight.nasa.gov/gallery/images/apollo/apollo17/hires/as17-148-22727.jpg"
]
let urls = urlStrings.flatMap { URL(string: $0) }
for url in urls {
BackgroundSession.shared.download(url)
}
}
}
If your app isn't running when the downloads finish, iOS needs to know that, after it restarted your app, when you're all done and that it can safely suspend your app. So, in handleEventsForBackgroundURLSession you capture that closure:
class AppDelegate: UIResponder, UIApplicationDelegate {
...
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: #escaping () -> Void) {
BackgroundSession.shared.saveBackgroundCompletionHandler(completionHandler)
}
}
That is used by sessionDidFinishEventsForBackgroundURLSession, in step 1.
Two observations:
This is only called if your app was not running when the downloads finish.
If doing background sessions, though, you must capture this closure and call it when you're all done processing the background session delegate methods.
So, to recap, the basic limitations of background sessions are:
You can only use download and upload tasks while the app is in background;
You can only rely upon session-level delegates because the app may have been terminated since the requests were initiated; and
In iOS, you must implement handleEventsForBackgroundURLSession, capture that completion handler, and call it when your background process is done.
I must also point out that while Alamofire is a wonderful library, it's not actually adding a lot value (above and beyond what is provided by URLSession to this background download process). If you're doing simple uploads/downloads only, then you might consider just using URLSession directly. But if you are using Alamofire in your project already or if your requests consist of more complicated application/x-www-form-urlencoded requests (or whatever) which merit the advantages of Alamofire, then the above outlines the key moving parts involved in the process.