DispatchSourceTimer crashes in deinit/dealloc - ios

I'm trying to use a DispatchSourceTimer to run a repeating timer on another thread. This code runs fine in a playground, however in my iOS app it keeps crashing either on the deinit method (or if I remove the deinit it crashes on dealloc for the thread its running on) and I can't figure out why. Is there a better way to use DispatchSourceTimer?
import UIKit
class DispatchTest {
var timer: DispatchSourceTimer
var count: Int = 0
init(timeInterval: TimeInterval) {
timer = DispatchSource.makeTimerSource(flags: .strict, queue: DispatchQueue.global(qos: .default))
timer.schedule(deadline: .now() + timeInterval, repeating: timeInterval, leeway: .milliseconds(100))
}
func startTimer() {
timer.setEventHandler(handler: {[weak self] in
self?.count += 1
if let count = self?.count {
print(count)
}
})
timer.resume()
}
deinit {
timer.setEventHandler {}
timer.cancel()
}
func stopTimer() {
self.timer.cancel()
}
}
let dispatch = DispatchTest(timeInterval: 1)
dispatch.startTimer()

I was experiencing the same problem. After digging around I have found the answer in Apple's Dispatch documentation.
https://developer.apple.com/documentation/dispatch/1452801-dispatch_suspend :
It is a programmer error to release an object that is currently suspended, because suspension implies that there is still work to be done. Therefore, always balance calls to this method with a corresponding call to dispatch_resume before disposing of the object. The behavior when releasing the last reference to a dispatch object while it is in a suspended state is undefined.
https://developer.apple.com/documentation/dispatch/1452929-dispatch_resume :
New dispatch event source objects returned by dispatch_source_create have a suspension count of 1
What I have deduced from this is that all new DispatchSource objects start suspended and thus the crash happens if you have not resumed the timer before deallocation.
The solution is to make sure the timer is in a resumed state before it is released.

Related

Subclassing OperationQueue adding sleep period

import Foundation
class MyOperationQueue {
static let shared = MyOperationQueue()
private var queue: OperationQueue
init() {
self.queue = OperationQueue()
queue.name = "com.myqueue.name"
queue.maxConcurrentOperationCount = 1
queue.qualityOfService = .background
}
func requestDataOperation() {
queue.addOperation {
print("START NETWORK \(Date())")
NetworkService.shared.getData()
print("END NETWORK \(Date())")
}
}
func scheduleSleep() {
queue.cancelAllOperations()
queue.addOperation {
print("SLEEP START \(Date())")
Thread.sleep(forTimeInterval: 5)
print("SLEEP END \(Date())")
}
}
func cancelAll() {
queue.cancelAllOperations()
}
}
I put requestDataOperation function inside a timer for every 10 seconds interval. And I have a button to call scheduleSleep manually. I was expected to debounce the request for every 5 more seconds when I tapping the button.
But I am getting something like this:
START NETWORK
END NETWORK
SLEEP START 2021-03-11 11:13:40 +0000
SLEEP END 2021-03-11 11:13:45 +0000
SLEEP START 2021-03-11 11:13:45 +0000
SLEEP END 2021-03-11 11:13:50 +0000
START NETWORK
END NETWORK
How to add 5 more seconds since my last tapping and combine it together rather than split it into two operation? I call queue.cancelAllOperations and start a new sleep operation but doesn't seem to work.
Expect result:
START NETWORK
END NETWORK
SLEEP START 2021-03-11 11:13:40 +0000
// <- the second tap when 2 seconds passed away
SLEEP END 2021-03-11 11:13:47 +0000 // 2+5
START NETWORK
END NETWORK
If you want some operation to be delayed for a certain amount of time, I would not create a “queue” class, but rather I would just define an Operation that simply will not be isReady until that time has passed (e.g., five seconds later). That not only eliminates the need for two separate “sleep operations”, but eliminates them altogether.
E.g.,
class DelayedOperation: Operation {
#Atomic private var enoughTimePassed = false
private var timer: DispatchSourceTimer?
private var block: (() -> Void)?
override var isReady: Bool { enoughTimePassed && super.isReady } // this operation won't run until (a) enough time has passed; and (b) any dependencies or the like are satisfied
init(timeInterval: TimeInterval = 5, block: #escaping () -> Void) {
self.block = block
super.init()
resetTimer(for: timeInterval)
}
override func main() {
block?()
block = nil
}
func resetTimer(for timeInterval: TimeInterval = 5) {
timer = DispatchSource.makeTimerSource() // create GCD timer (eliminating reference to any prior timer will cancel that one)
timer?.setEventHandler { [weak self] in
guard let self = self else { return }
self.willChangeValue(forKey: #keyPath(isReady)) // make sure to do necessary `isReady` KVO notification
self.enoughTimePassed = true
self.didChangeValue(forKey: #keyPath(isReady))
}
timer?.schedule(deadline: .now() + timeInterval)
timer?.resume()
}
}
I am synchronizing my interaction with enoughTimePassed with the following property wrapper, but you can use whatever synchronization mechanism you want:
#propertyWrapper
struct Atomic<Value> {
private var value: Value
private var lock = NSLock()
init(wrappedValue: Value) {
value = wrappedValue
}
var wrappedValue: Value {
get { synchronized { value } }
set { synchronized { value = newValue } }
}
private func synchronized<T>(block: () throws -> T) rethrows -> T {
lock.lock()
defer { lock.unlock() }
return try block()
}
}
Just make sure that isReady is thread-safe.
Anyway, having defined that DelayedOperation, then you can do something like
logger.debug("creating operation")
let operation = DelayedOperation {
logger.debug("some task")
}
queue.addOperation(operation)
And it will delay running that task (in this case, just login “some task” message) for five seconds. If you want to reset the timer, just call that method on the operation subclass:
operation.resetTimer()
For example, here I created the task, added it to the queue, reset it three times at two second intervals, and the block actually runs five seconds after the last reset:
2021-09-30 01:13:12.727038-0700 MyApp[7882:228747] [ViewController] creating operation
2021-09-30 01:13:14.728953-0700 MyApp[7882:228747] [ViewController] delaying operation
2021-09-30 01:13:16.728942-0700 MyApp[7882:228747] [ViewController] delaying operation
2021-09-30 01:13:18.729079-0700 MyApp[7882:228747] [ViewController] delaying operation
2021-09-30 01:13:23.731010-0700 MyApp[7882:228829] [ViewController] some task
Now, if you're using operations for network requests, then you are presumably already implemented your own asynchronous Operation subclass that does the necessary KVO for isFinished, isExecuting, etc., so you may choose marry the above isReady logic with that existing Operation subclass.
But the idea is one can completely lose the "sleep" operation with an asynchronous pattern. If you did want a dedicate sleep operation, you could still use the above pattern (but make it an asynchronous operation rather than blocking a thread with sleep).
All of this having been said, if I personally wanted to debounce a network request, I would not integrate this into the operation or operation queue. I would just do that debouncing at the time that I started the request:
weak var timer: Timer?
func debouncedRequest(in timeInterval: TimeInterval = 5) {
timer?.invalidate()
timer = .scheduledTimer(withTimeInterval: timeInterval, repeats: false) { _ in
// initiate request here
}
}

Adding Tme Countdown to App Using a Thread

I'm trying to add a countdown timer to an existing app. Naturally, I don't want this timer to stall the rest of the application so I wanted to use an asynchronous thread dedicated to the timer. This is my current code and it doesn't even get to the update function (I used print statements to test this), but does print "Got". Also, I'm trying to update a label with the correct time and you can't do that within the thread. The time variable is a class variable. Not sure if this is even the correct approach, any suggestions?
Edit: Running the timer on main queue doesn't work as it interferes with a pan gesture I already have on the app. Also, any proposed solutions to the timing inaccuracies of the Timer Class would also be great.
func startTimer() {
time = 30
let queue = DispatchQueue(label: "timer")
queue.async {
print("Got")
_ = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(self.update), userInfo: nil, repeats: true)
}
}
func update() {
DispatchQueue.main.sync {
if(time >= 0) {
time -= 1
timer.text = String(time)
} else {
timer.text = "0"
}
}
}

Memory growth when using DispatchQueue. Instruments shows growth in non-object objects

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 !!!

Repeating background task Swift 3 DispatchSourceTimer [duplicate]

I can't figure out how to make dispatch timer work repeatedly in Swift 3.0. My code:
let queue = DispatchQueue(label: "com.firm.app.timer",
attributes: DispatchQueue.Attributes.concurrent)
let timer = DispatchSource.makeTimerSource(flags: DispatchSource.TimerFlags(rawValue: UInt(0)),
queue: queue)
timer.scheduleRepeating(deadline: DispatchTime.now(),
interval: .seconds(5),
leeway: .seconds(1)
)
timer.setEventHandler(handler: {
//a bunch of code here
})
timer.resume()
Timer just fires one time and doesn't repeat itself like it should be. How can I fix this?
Make sure the timer doesn't fall out of scope. Unlike Timer (where the RunLoop on which you schedule it keeps the strong reference until the Timer is invalidated), you need to maintain your own strong reference to your GCD timers, e.g.:
private var timer: DispatchSourceTimer?
private func startTimer() {
let queue = DispatchQueue(label: "com.firm.app.timer", attributes: .concurrent)
timer = DispatchSource.makeTimerSource(queue: queue)
timer?.setEventHandler { [weak self] in // `[weak self]` only needed if you reference `self` in this closure and you want to prevent strong reference cycle
print(Date())
}
timer?.schedule(deadline: .now(), repeating: .seconds(5), leeway: .milliseconds(100))
timer?.resume()
}
private func stopTimer() {
timer = nil
}

How to create a delay in Swift?

I want to pause my app at a certain in point. In other words, I want my app to execute the code, but then at a certain point, pause for 4 seconds, and then continue on with the rest of the code. How can I do this?
I am using Swift.
Using a dispatch_after block is in most cases better than using sleep(time) as the thread on which the sleep is performed is blocked from doing other work. when using dispatch_after the thread which is worked on does not get blocked so it can do other work in the meantime. If you are working on the main thread of your application, using sleep(time) is bad for the user experience of your app as the UI is unresponsive during that time.
Dispatch after schedules the execution of a block of code instead of freezing the thread:
Swift ≥ 3.0
let seconds = 4.0
DispatchQueue.main.asyncAfter(deadline: .now() + seconds) {
// Put your code which should be executed with a delay here
}
Swift ≥ 5.5 in an async context:
func foo() async {
try await Task.sleep(nanoseconds: UInt64(seconds * Double(NSEC_PER_SEC)))
// Put your code which should be executed with a delay here
}
Swift < 3.0
let time = dispatch_time(dispatch_time_t(DISPATCH_TIME_NOW), 4 * Int64(NSEC_PER_SEC))
dispatch_after(time, dispatch_get_main_queue()) {
// Put your code which should be executed with a delay here
}
Instead of a sleep, which will lock up your program if called from the UI thread, consider using NSTimer or a dispatch timer.
But, if you really need a delay in the current thread:
do {
sleep(4)
}
This uses the sleep function from UNIX.
Comparison between different approaches in swift 3.0
1. Sleep
This method does not have a call back. Put codes directly after this line to be executed in 4 seconds. It will stop user from iterating with UI elements like the test button until the time is gone. Although the button is kind of frozen when sleep kicks in, other elements like activity indicator is still spinning without freezing. You cannot trigger this action again during the sleep.
sleep(4)
print("done")//Do stuff here
2. Dispatch, Perform and Timer
These three methods work similarly, they are all running on the background thread with call backs, just with different syntax and slightly different features.
Dispatch is commonly used to run something on the background thread. It has the callback as part of the function call
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(4), execute: {
print("done")
})
Perform is actually a simplified timer. It sets up a timer with the delay, and then trigger the function by selector.
perform(#selector(callback), with: nil, afterDelay: 4.0)
func callback() {
print("done")
}}
And finally, timer also provides ability to repeat the callback, which is not useful in this case
Timer.scheduledTimer(timeInterval: 4, target: self, selector: #selector(callback), userInfo: nil, repeats: false)
func callback() {
print("done")
}}
For all these three method, when you click on the button to trigger them, UI will not freeze and you are allowed to click on it again. If you click on the button again, another timer is set up and the callback will be triggered twice.
In conclusion
None of the four method works good enough just by themselves. sleep will disable user interaction, so the screen "freezes"(not actually) and results bad user experience. The other three methods will not freeze the screen, but you can trigger them multiple times, and most of the times, you want to wait until you get the call back before allowing user to make the call again.
So a better design will be using one of the three async methods with screen blocking. When user click on the button, cover the entire screen with some translucent view with a spinning activity indicator on top, telling user that the button click is being handled. Then remove the view and indicator in the call back function, telling user that the the action is properly handled, etc.
In Swift 4.2 and Xcode 10.1
You have 4 ways total to delay. Out of these option 1 is preferable to call or execute a function after some time. The sleep() is least case in use.
Option 1.
DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
self.yourFuncHere()
}
//Your function here
func yourFuncHere() {
}
Option 2.
perform(#selector(yourFuncHere2), with: nil, afterDelay: 5.0)
//Your function here
#objc func yourFuncHere2() {
print("this is...")
}
Option 3.
Timer.scheduledTimer(timeInterval: 5.0, target: self, selector: #selector(yourFuncHere3), userInfo: nil, repeats: false)
//Your function here
#objc func yourFuncHere3() {
}
Option 4.
sleep(5)
If you want to call a function after some time to execute something don't use sleep.
I agree with Palle that using dispatch_after is a good choice here. But you probably don't like the GCD calls as they are quite annoying to write. Instead you can add this handy helper:
public func delay(bySeconds seconds: Double, dispatchLevel: DispatchLevel = .main, closure: #escaping () -> Void) {
let dispatchTime = DispatchTime.now() + seconds
dispatchLevel.dispatchQueue.asyncAfter(deadline: dispatchTime, execute: closure)
}
public enum DispatchLevel {
case main, userInteractive, userInitiated, utility, background
var dispatchQueue: DispatchQueue {
switch self {
case .main: return DispatchQueue.main
case .userInteractive: return DispatchQueue.global(qos: .userInteractive)
case .userInitiated: return DispatchQueue.global(qos: .userInitiated)
case .utility: return DispatchQueue.global(qos: .utility)
case .background: return DispatchQueue.global(qos: .background)
}
}
}
Now you simply delay your code on a background thread like this:
delay(bySeconds: 1.5, dispatchLevel: .background) {
// delayed code that will run on background thread
}
Delaying code on the main thread is even simpler:
delay(bySeconds: 1.5) {
// delayed code, by default run in main thread
}
If you prefer a Framework that also has some more handy features then checkout HandySwift. You can add it to your project via SwiftPM then use it exactly like in the examples above:
import HandySwift
delay(by: .seconds(1.5)) {
// delayed code
}
You can also do this with Swift 3.
Perform the function after delay like so.
override func viewDidLoad() {
super.viewDidLoad()
self.perform(#selector(ClassName.performAction), with: nil, afterDelay: 2.0)
}
#objc func performAction() {
//This function will perform after 2 seconds
print("Delayed")
}
NSTimer
The answer by #nneonneo suggested using NSTimer but didn't show how to do it. This is the basic syntax:
let delay = 0.5 // time in seconds
NSTimer.scheduledTimerWithTimeInterval(delay, target: self, selector: #selector(myFunctionName), userInfo: nil, repeats: false)
Here is a very simple project to show how it might be used. When a button is pressed it starts a timer that will call a function after a delay of half a second.
import UIKit
class ViewController: UIViewController {
var timer = NSTimer()
let delay = 0.5
// start timer when button is tapped
#IBAction func startTimerButtonTapped(sender: UIButton) {
// cancel the timer in case the button is tapped multiple times
timer.invalidate()
// start the timer
timer = NSTimer.scheduledTimerWithTimeInterval(delay, target: self, selector: #selector(delayedAction), userInfo: nil, repeats: false)
}
// function to be called after the delay
func delayedAction() {
print("action has started")
}
}
Using dispatch_time (as in Palle's answer) is another valid option. However, it is hard to cancel. With NSTimer, to cancel a delayed event before it happens, all you need to do is call
timer.invalidate()
Using sleep is not recommended, especially on the main thread, since it stops all the work being done on the thread.
See here for my fuller answer.
Try the following implementation in Swift 3.0
func delayWithSeconds(_ seconds: Double, completion: #escaping () -> ()) {
DispatchQueue.main.asyncAfter(deadline: .now() + seconds) {
completion()
}
}
Usage
delayWithSeconds(1) {
//Do something
}
If you need to set a delay of less than a second, it is not necessary to set the .seconds parameter. I hope this is useful to someone.
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: {
// your code hear
})
You can create extension to use delay function easily (Syntax: Swift 4.2+)
extension UIViewController {
func delay(_ delay:Double, closure:#escaping ()->()) {
DispatchQueue.main.asyncAfter(
deadline: DispatchTime.now() + Double(Int64(delay * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC), execute: closure)
}
}
How to use in UIViewController
self.delay(0.1, closure: {
//execute code
})
I believe the simplest and latest way of doing a 4 seconds timer is:
Task {
// Do something
// Wait for 4 seconds
try await Task.sleep(nanoseconds: 4_000_000_000)
}
It uses Swift 5.5's new concurrency.
If your code is already running in a background thread, pause the thread using this method in Foundation: Thread.sleep(forTimeInterval:)
For example:
DispatchQueue.global(qos: .userInitiated).async {
// Code is running in a background thread already so it is safe to sleep
Thread.sleep(forTimeInterval: 4.0)
}
(See other answers for suggestions when your code is running on the main thread.)
DispatchQueue.global(qos: .background).async {
sleep(4)
print("Active after 4 sec, and doesn't block main")
DispatchQueue.main.async{
//do stuff in the main thread here
}
}
To create a simple time delay, you can import Darwin and then use sleep(seconds) to do the delay. That only takes whole seconds, though, so for more precise measurements you can import Darwin and use usleep(millionths of a second) for very precise measurement. To test this, I wrote:
import Darwin
print("This is one.")
sleep(1)
print("This is two.")
usleep(400000)
print("This is three.")
Which prints, then waits for 1 sec and prints, then waits for 0.4 sec then prints. All worked as expected.
Using DispatchQueue's .asyncAfter method you can execute code after given time. So, e.g. execute ... on main thread after 1 second looks like this:
DispatchQueue.main.asyncAfter(deadline: .now() + 1) { ... }
Using my handy Delay wrapper struct you can execute it in more fancy way:
struct Delay {
#discardableResult
init(_ timeInterval: TimeInterval, queue: DispatchQueue = .main, executingBlock: #escaping () -> Void) {
queue.asyncAfter(deadline: .now() + timeInterval, execute: executingBlock)
}
}
Usage:
Delay(0.4) { ... }
Swift 5<
Using Task.sleep will not block any code other than the task at hand, and it's pretty straightforward.
//Delay task by 4 seconds:
Task {
try await Task.sleep(nanoseconds: 4000000000)
//Execute your code here
}
This is a simpler way of adding a delay that doesn't affect thread execution.
let continueTime: Date = Calendar.current.date(byAdding: .second, value: 30, to: Date())!
while (Date() < continueTime) {
//DO NOTHING
}
As an alternative solution to the previously proposed options, you can use a delay based on the DispatchGroup class, which is designed to synchronise the execution of several asynchronous tasks:
print("Start")
print(Date())
let delay = DispatchTimeInterval.seconds(3)
let group = DispatchGroup()
group.enter()
_ = group.wait(timeout: .now() + delay)
print("Finish")
print(Date())
Where the enter() method is used to explicitly indicate that the execution of the group code has begun and wait(timeout:) method to wait for group tasks to complete. Of course, in this example this will never happen, for this a timeout is specified, which is equal to the required delay.
It is convenient to use it as a ready-made helper:
public class DispatchWait {
private init () { }
public static func `for` (_ interval: DispatchTimeInterval) {
let group = DispatchGroup()
group.enter()
_ = group.wait(timeout: .now().advanced(by: interval))
}
}
An example of using the DispatchWait:
print("Start")
print(Date())
DispatchWait.for(.seconds(3))
print("Finish")
print(Date())
Unfortunately, I cannot say what is the accuracy of this delay, and what is the probability that the wait(timeout:) method will allow further execution of the program much later than the specified delay.
Also, this solution allows you to delay the code in the current queue, without having to execute it in a separate closure.
this is the simplest
delay(0.3, closure: {
// put her any code you want to fire it with delay
button.removeFromSuperview()
})

Resources