How to Efficiently Schedule Thousands of Async Events Using Swift 3 DispatchQueue - ios

I am moving a map marker 1 metre every 0.1 seconds with the following code:
for index in 1 ... points.count - 1 {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1 * Double(index)) {
self.driverMarker.position = points[index]
self.driverMarker.map = self.mapView
}
}
If the distance of all the points is 3000 metres then I set setting 3000 asyncAfters and I am worried this is inefficient.
Is there a better way to do this?

From your requirements stated in the question and comments, I believe using DispatchSourceTimer is better for this task. I provided a sample code for reference below.
var count = 0
var bgTimer: DispatchSourceTimer?
func translateMarker() {
if count == (points.count - 1) {
bgTimer?.cancel()
bgTimer = nil
return
}
self.driverMarker.position = points[index]
self.driverMarker.map = self.mapView
count += 1
}
override func viewDidLoad() {
super.viewDidLoad()
let queue:DispatchQueue = DispatchQueue.global(qos: DispatchQoS.QoSClass.default)
bgTimer = DispatchSource.makeTimerSource(flags: [], queue: queue)
bgTimer?.scheduleRepeating(deadline: DispatchTime.now(), interval: 0.1)
bgTimer?.setEventHandler(handler: {
self.translateMarker()
})
bgTimer?.resume()
}
Please let me know if you are facing any issues in implementing this. Feel free to suggest edits to make this better :)

Related

AudioKit playback cracks

I want to analyze the microphone input frequency and then play the correct note which is near the frequency which was determined. I did that with of AudioKit.
This is working right now but since I implemented AudioKit to get the frequency feature the sound which plays after the frequency detection cracks sometimes during playback. Thats happened after I implemented AudioKit. Everything was fine before that...
var mic: AKMicrophone!
var tracker: AKFrequencyTracker!
var silence: AKBooster!
func initFrequencyTracker() {
AKSettings.channelCount = 2
AKSettings.audioInputEnabled = true
AKSettings.defaultToSpeaker = true
AKSettings.allowAirPlay = true
AKSettings.useBluetooth = true
AKSettings.allowHapticsAndSystemSoundsDuringRecording = true
mic = AKMicrophone()
tracker = AKFrequencyTracker(mic)
silence = AKBooster(tracker, gain: 0)
}
func deinitFrequencyTracker() {
AKSettings.audioInputEnabled = false
plotTimer.invalidate()
do {
try AudioKit.stop()
AudioKit.output = nil
} catch {
print(error)
}
}
func initPlotTimer() {
AudioKit.output = silence
do {
try AKSettings.setSession(category: .playAndRecord, with: [.defaultToSpeaker, .allowBluetooth, .allowAirPlay, .allowBluetoothA2DP])
try AudioKit.start()
} catch {
AKLog("AudioKit did not start!")
}
setupPlot()
plotTimer = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(updatePlotUI), userInfo: nil, repeats: true)
}
func setupPlot() {
let plot = AKNodeOutputPlot(mic, frame: audioInputPlot.bounds)
plot.translatesAutoresizingMaskIntoConstraints = false
plot.alpha = 0.3
plot.plotType = .rolling
plot.shouldFill = true
plot.shouldCenterYAxis = false
plot.shouldMirror = true
plot.color = UIColor(named: uiFarbe)
audioInputPlot.addSubview(plot)
// Pin the AKNodeOutputPlot to the audioInputPlot
var constraints = [plot.leadingAnchor.constraint(equalTo: audioInputPlot.leadingAnchor)]
constraints.append(plot.trailingAnchor.constraint(equalTo: audioInputPlot.trailingAnchor))
constraints.append(plot.topAnchor.constraint(equalTo: audioInputPlot.topAnchor))
constraints.append(plot.bottomAnchor.constraint(equalTo: audioInputPlot.bottomAnchor))
constraints.forEach { $0.isActive = true }
}
#objc func updatePlotUI() {
if tracker.amplitude > 0.3 {
let trackerFrequency = Float(tracker.frequency)
guard trackerFrequency < 7_000 else {
// This is a bit of hack because of modern Macbooks giving super high frequencies
return
}
var frequency = trackerFrequency
while frequency > Float(noteFrequencies[noteFrequencies.count - 1]) {
frequency /= 2.0
}
while frequency < Float(noteFrequencies[0]) {
frequency *= 2.0
}
var minDistance: Float = 10_000.0
var index = 0
for i in 0..<noteFrequencies.count {
let distance = fabsf(Float(noteFrequencies[i]) - frequency)
if distance < minDistance {
index = i
minDistance = distance
}
print(minDistance, distance)
}
// let octave = Int(log2f(trackerFrequency / frequency))
frequencyLabel.text = String(format: "%0.1f", tracker.frequency)
if frequencyTranspose(note: notesToTanspose[index]) != droneLabel.text {
momentaneNote = frequencyTranspose(note: notesToTanspose[index])
droneLabel.text = momentaneNote
stopSinglePlayer()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.03, execute: {
self.prepareSinglePlayerFirstForStart(note: self.momentaneNote)
self.startSinglePlayer()
})
}
}
}
func frequencyTranspose(note: String) -> String {
var indexNote = notesToTanspose.firstIndex(of: note)!
let chosenInstrument = UserDefaults.standard.object(forKey: "whichInstrument") as! String
if chosenInstrument == "Bb" {
if indexNote + 2 >= notesToTanspose.count {
indexNote -= 12
}
return notesToTanspose[indexNote + 2]
} else if chosenInstrument == "Eb" {
if indexNote - 3 < 0 {
indexNote += 12
}
return notesToTanspose[indexNote - 3]
} else {
return note
}
}
Appears that your implementation can be improved slightly by putting the multithreading principles of iOS into practice. Now, I'm not an expert in the subject, but if we look into the statement: "the sound which plays after the frequency detection cracks sometimes during playback".
I'd like to point out that the "frequency" of the "crack" is random or unpredictable and this happens during computation.
So, move code that doesn't need to be computed in the main thread to a background thread (https://developer.apple.com/documentation/DISPATCH)
While refactoring, you can test your implementation by increasing the frequency of calls to the callback computation of your Timer, so reduce the value to 0.05 for example. Which means that if you increase the frequency to, let's say 0.2, you'll probably hear less random crackles.
Now, this is easier said than done when considering concurrency but that's what you need to improve.

Drawing app with timer. Timers begins to lag after drawing for less than 20 seconds

I have built this app with the help of some friends. I don't really know how the code works.
Basically using an apple pencil it records data (time on tablet, speed of apple pencil, stroke counts etc). However as more time elapses and more drawing occurs, the timer gets out of sync with real time.
The purpose of this app is for dementia research, I get patients to draw on the tablet, and i collect information of that. I can't do the research if the timer stinks.
I have tried disabling all the timers, but the lag remains the same. I have a felling it has something to do with how strokes are being sampled. I just need a stroke count I don't need it to show strokes per min (which is what it is currently doing). I think the stroke counter might the cause???
this is the program:
https://drive.google.com/open?id=1lwzKwG7NLcX1qmE5yoxsdq5HICV2TNHm
class StrokeSegment {
var sampleBefore: StrokeSample?
var fromSample: StrokeSample!
var toSample: StrokeSample!
var sampleAfter: StrokeSample?
var fromSampleIndex: Int
var segmentUnitNormal: CGVector {
return segmentStrokeVector.normal!.normalized!
}
var fromSampleUnitNormal: CGVector {
return interpolatedNormalUnitVector(between: previousSegmentStrokeVector, and: segmentStrokeVector)
}
var toSampleUnitNormal: CGVector {
return interpolatedNormalUnitVector(between: segmentStrokeVector, and: nextSegmentStrokeVector)
}
var previousSegmentStrokeVector: CGVector {
if let sampleBefore = self.sampleBefore {
return fromSample.location - sampleBefore.location
} else {
return segmentStrokeVector
}
}
var segmentStrokeVector: CGVector {
return toSample.location - fromSample.location
}
var nextSegmentStrokeVector: CGVector {
if let sampleAfter = self.sampleAfter {
return sampleAfter.location - toSample.location
} else {
return segmentStrokeVector
}
}
init(sample: StrokeSample) {
self.sampleAfter = sample
self.fromSampleIndex = -2
}
#discardableResult
func advanceWithSample(incomingSample: StrokeSample?) -> Bool {
if let sampleAfter = self.sampleAfter {
self.sampleBefore = fromSample
self.fromSample = toSample
self.toSample = sampleAfter
self.sampleAfter = incomingSample
self.fromSampleIndex += 1
return true
}
return false
}
}
class StrokeSegmentIterator: IteratorProtocol {
private let stroke: Stroke
private var nextIndex: Int
private let sampleCount: Int
private let predictedSampleCount: Int
private var segment: StrokeSegment!
init(stroke: Stroke) {
self.stroke = stroke
nextIndex = 1
sampleCount = stroke.samples.count
predictedSampleCount = stroke.predictedSamples.count
if (predictedSampleCount + sampleCount) > 1 {
segment = StrokeSegment(sample: sampleAt(0)!)
segment.advanceWithSample(incomingSample: sampleAt(1))
}
}
func sampleAt(_ index: Int) -> StrokeSample? {
if index < sampleCount {
return stroke.samples[index]
}
let predictedIndex = index - sampleCount
if predictedIndex < predictedSampleCount {
return stroke.predictedSamples[predictedIndex]
} else {
return nil
}
}
func next() -> StrokeSegment? {
nextIndex += 1
if let segment = self.segment {
if segment.advanceWithSample(incomingSample: sampleAt(nextIndex)) {
return segment
}
}
return nil
}
}
for example at true 25 seconds, the app displays the total time at 20 seconds.
A Timer is not something to count elapsed time. It is a tool used to trigger an execution after some time has elapsed. But just "after" some time has elapsed, not "exactly after" some time has elapsed. So for instance doing something like:
var secondsElapsed: TimeInterval = 0.0
let timeInitiated = Date()
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
secondsElapsed += 1
print("\(secondsElapsed) seconds should have passed but in reality \(Date().timeIntervalSince(timeInitiated)) second have passed")
}
you will see that the two are not the same but are pretty close. But as soon as I add some extra work like this:
var secondsElapsed: TimeInterval = 0.0
let timeInitiated = Date()
func countTo(_ end: Int) {
var string = ""
for i in 1...end {
string += String(i)
}
print("Just counted to string of lenght \(string.count)")
}
Timer.scheduledTimer(withTimeInterval: 1.0/60.0, repeats: true) { _ in
countTo(100000)
}
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
secondsElapsed += 1
print("\(secondsElapsed) seconds should have passed but in reality \(Date().timeIntervalSince(timeInitiated)) second have passed")
}
we get to situations like "14.0 seconds should have passed but in reality 19.17617702484131 second have passed".
We made the application busy so it doesn't have time to count correctly.
In your case you will need to use one of two solutions:
If you are interested in time elapsed simply use timeIntervalSince as demonstrated in first code snippet.
If you need to ensure triggering every N seconds you should optimize your code, consider multithreading... But mostly keep in mind that you can only get close to "every N seconds", it should not be possible to guarantee an execution exactly every N seconds.

How to put delay for each iteration of loop

Say I had this loop:
count = 0
for i in 0...9 {
count += 1
}
and I want to delay it.
Delay function:
// Delay function
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)
}
.
This means if I want to increase count by 1 every second, I would do:
count = 0
for i in 0...9 {
delay(1) {
count += 1
}
}
but this doesn't work as it only delays code in brackets. How do I delay the actual loop? I would like the delay to stop from iterating until the time has passed, and then the loop/code can repeat again.
Your current code doesn't work because you are doing the increment asynchronously. This means that the for loop will still run at its normal speed.
To achieve what you want, you can use a timer like this:
var count = 0
let timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true){ _ in
count += 1
print(count)
}
If you want it to stop after 5 times, do this:
var count = 0
var timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true){ t in
count += 1
print(count)
if count >= 5 {
t.invalidate()
}
}
As #Paulw11 and #Sweeper have suggested, you can use a Timer to do this. However, if the code does need to be asynchronous for some reason, you can reimplement the loop asynchronously by making it recursive:
func loop(times: Int) {
var i = 0
func nextIteration() {
if i < times {
print("i is \(i)")
i += 1
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(1)) {
nextIteration()
}
}
}
nextIteration()
}

GCD Cancel and restart queue when button pressed again

Simply, I have a button that adds to a queue, or I believe that's how it works - I've only just started to use GCD. If I press the button twice in my app, it runs these two processes in parallel and the progress bar races back and forth between them, I'd like to stop all the other instances of the queue and execute a new one if that makes sense, I've not got to grips with the lingo...
#IBAction func makePi(_ sender: UIButton) {
piProgress.progress = 0.0
let queue1 = DispatchQueue(label: "com.appcoda.queue1", qos: DispatchQoS.userInitiated)
queue1.async {
let currentAmount = Int(self.randNum.value)
var gcdCounter = 0
for n in 1...currentAmount {
if self.gcd(self.random(max: Int(self.randRange.value)), self.random(max: Int(self.randRange.value))) == 1{
gcdCounter += 1
}
if n % 1000 == 0 {
DispatchQueue.main.async {
self.piProgress.setProgress(Float(Double(n)/Double(currentAmount)), animated: true)
}
} else if n == currentAmount {
DispatchQueue.main.async {
self.piProgress.setProgress(1.0, animated: true)
}
}
}
let piFinal = sqrt(Double((6/Double((Double(gcdCounter)/Double(currentAmount))))))
let roundedAccuracy: Double = ((piFinal-Double.pi)/Double.pi*100).roundTo(places: 6)
DispatchQueue.main.async {
self.piLabel.text = String(piFinal)
self.piAccuracy.text = "Accuracy: \(roundedAccuracy)%"
}
}
}
I just need all the other queue1.async ones to stop...
P.s. If I've done anything obviously wrong, please help ^^ Thanks.

In Gameplaykit, how can I add a delay GKGoal time in GKAgent2D behavior?

I have a GKEntity that has a GKAgent2D component. Its behaviors are GKGoal, toWander: and toStayOnPath:maxPredictionTime:. The entity wanders continuously in the scene; however, I would like it to stop wandering for a while. For example, if the entity is a sheep that wanders about, I would like it to stop periodically to eat and, after a delay, start wandering again.
UPDATE:
In the Entity:
addComponent(MoveIdleComponent(maxSpeed: 60, maxAcceleration: 6, radius: Float(node.texture!.size().width * 0.3), entityManager: entityManager))
MoveIdleComponent
class MoveIdleComponent : GKAgent2D, GKAgentDelegate {
let entityManager: EntityManager
init(maxSpeed: Float, maxAcceleration: Float, radius: Float, entityManager: EntityManager) {
self.entityManager = entityManager
super.init()
delegate = self
self.maxSpeed = maxSpeed
self.maxAcceleration = maxAcceleration
self.radius = radius
print(self.mass)
self.mass = 0.01
}
func agentWillUpdate(agent: GKAgent) {
guard let spriteComponent = entity?.componentForClass(SpriteComponent.self) else {
return
}
self.position = float2(spriteComponent.node.position)
}
func agentDidUpdate(agent: GKAgent) {
guard let spriteComponent = entity?.componentForClass(SpriteComponent.self) else {
return
}
spriteComponent.node.position = CGPoint(position)
}
override func updateWithDeltaTime(seconds: NSTimeInterval) {
super.updateWithDeltaTime(seconds)
behavior = WanderBehavoir(targetSpeed: maxSpeed)
}
}
WanderBehavoir:
class WanderBehavoir: GKBehavior {
init(targetSpeed: Float) {
super.init()
if targetSpeed > 0 {
setWeight(0.5, forGoal: GKGoal(toWander: targetSpeed))
}
}
}
How can I do this?
Thanks in advance
There doesn't seem to be a GKGoal(toEatFood:withFrequency:) API, so you'll have to step back a bit toandthink about how to set the agent's goals to achieve your goals.
If you want the agent to stop wandering, or stop following a path, for some period of time, what you want is for those to no longer be its goals. (And furthermore, you want it to stop, not continue with whatever direction and speed when you took away its goals, so you'll want to introduce a toReachTargetSpeed: goal for a speed of zero.)
There are two general ways to do this:
Have your behavior include wander, follow-path, and speed (of zero) goals, with weights set such that wander and follow-path outweigh speed. When you want to switch between wander+path behavior and stopping behavior, use setWeight(_:forGoal:) to make the speed goal outweigh the others.
Have one behavior that includes wander and follow-path goals, and another with a speed (of zero) goal, and set the agent's behavior property when you want to switch between them.
Ok, i found a solution with #rickster suggestion. I post it if can help someone.
I've added pause value.
if pause is true, the weight of speed change to 1, with GKGoal(toReachTargetSpeed: 0)
in WanderBehavoir class:
class WanderBehavoir: GKBehavior {
init(targetSpeed: Float, entityManager: EntityManager, pause: Bool) {
super.init()
var weightWander : Float = 1.0
var weightSpeed : Float = 0.0
if pause {
weightWander = 0
weightSpeed = 1
}
// | |
// --A--B--
// | |
if targetSpeed > 0 {
let lato = Float(500.0)
let pts = [vector_float2(-lato,0),vector_float2(+lato,0)]
let path = GKPath(points: UnsafeMutablePointer(pts), count: pts.count, radius: 980, cyclical: true)
let obstacleNode = entityManager.nodesWithUnitType(Tree)
let obstacles = SKNode.obstaclesFromNodePhysicsBodies(obstacleNode)
setWeight(0.5, forGoal: GKGoal(toAvoidObstacles: obstacles, maxPredictionTime: 0.5))
setWeight(0.2, forGoal: GKGoal(toStayOnPath: path, maxPredictionTime: 0.5))
setWeight(weightWander, forGoal: GKGoal(toWander: targetSpeed))
setWeight(weightSpeed, forGoal: GKGoal(toReachTargetSpeed: 0))
}
}
}
In class class MoveIdleComponent : GKAgent2D, GKAgentDelegate pause switch true and false after delta time randomly
private var lastUpdateInterval: NSTimeInterval = 0
var setPause = true
var randomTimeStop = NSTimeInterval(Int(5...8))
var randomTimeMove = NSTimeInterval(Int(10...20))
override func updateWithDeltaTime(seconds: NSTimeInterval) {
super.updateWithDeltaTime(seconds)
lastUpdateInterval += seconds
if setPause {
if lastUpdateInterval > randomTimeStop {
setPause = !setPause
lastUpdateInterval = 0
randomTimeMove = NSTimeInterval(Int(10...20))
}
}
else {
if lastUpdateInterval > randomTimeMove {
setPause = !setPause
lastUpdateInterval = 0
randomTimeStop = NSTimeInterval(Int(5...8))
}
}
print("randomTimeMove \(randomTimeMove)")
behavior = WanderBehavoir(targetSpeed: maxSpeed, entityManager: entityManager, pause: setPause)
}

Resources