Loops and Dispatch Queue - swift2

I have a dispatch queue which has to be run infinitely. So am trying to have it in a do while loop so that the thread runs continuously, but when I tried to do it, I get a blank screen.
Below is the code:
var i = 1
do{
dispatch.main(3)
{
self.myfunction()
}
i+=1
}while(i>0)
I do not understand, why is it happening? and any idea on how to get a dispatch called every few seconds?

Here is an extension of NSTimer, from EZSwiftExtensions.
extension NSTimer {
public static func runThisEvery(seconds seconds: NSTimeInterval, handler: NSTimer! -> Void) -> NSTimer {
let fireDate = CFAbsoluteTimeGetCurrent()
let timer = CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault, fireDate, seconds, 0, 0, handler)
CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopCommonModes)
return timer
}
}
What the code does is easy to understand. Create a run loop timer, add it to the run loop so it can actually run and fire, and return that instance so you can stop it later.
let timer = NSTimer.runThisEvery(seconds: 3) { _ in self.myFunction() }
to stop it, just:
timer.invalidate()

Related

want to subtract current time with event time (which has comes from my json api) in swift 5

i have an api which gives me the Event time like movie time And i want to Add a countdown Time in my apps that tells me how much time Are Left to Start a movie.
Your code is not displayed but I hope this will help.
// use repeating scheduledTimer(withTimeInterval:repeats:block:) method for the Timer.
// Initialize the timer and save in the property for invalidating
private let step: Double = 1.0
private var timer: Timer?
private func initTimer() {
let action: (Timer)-> Void = { [weak self] timer in
guard let StrongSelf = self else {
return
}
}
}
timer = Timer.scheduledTimer(withTimeInterval: step, repeats: true, block: action)
private func deinitTimer() {
timer?,invalidate()
timer = nil
}
// To Display
func timeString(from timeInterval: TimeInterval) -> String {
let seconds = Int(timeInterval.turncatingRemainder(dividingBy: 60))
let minutes = Int(timeInterval.turncatingRemainder(dividingBy: 60 * 60) / 60)
let hours = Int(timeInterval / 3600)
return String(format: "%.value:%.value:%.value" , hours, minutes, seconds
}
// Pause & Resume
stopwatch.toggle()
//Reset
stopwatch.stop()

How to preform a delay in Xcode, unitests, without blocking the main thread?

I have a test that in it I have to do a REST call, to a server,
and only after that call is recieved I can continue with the rest of the tests,
I cannot use sleep(x) as it will block the main thread, and won't let the rest call to be done.
here is a code example to better explain:
let homeInteractor: HomeInteractor = HomeInteractor ()
homeInteractor.initSDK()
//// <<<<< DO A DELEY HERE FOR 5 SECONDS
//sleep(5) < CANNOT USE SLEEP AS IT WILL BLOCK THE MAIN THREAD
let loginInteractor: LoginInteractor = LoginInteractor ()
let loginViewController: LoginTableViewController = LoginTableViewController ()
let loginPresenter: LoginPresenter = LoginPresenter(controller: loginViewController)
loginPresenter.onLoginButtonPressed(usernameText: "username", passwordText: "password")
waitForExpectations(timeout: 10)
XCTAssertEqual(loginPresenter.didUserLogin, true)
How can such a delay be achieved?
Try this:
// The code you didn't know when will finish
let homeInteractor: HomeInteractor = HomeInteractor ()
homeInteractor.initSDK()
// The time you guess it needs
let delay: TimeInterval = 5
let expectation = XCTestExpectation()
// Rest of the code after that delay
DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
let loginInteractor: LoginInteractor = LoginInteractor ()
let loginViewController: LoginTableViewController = LoginTableViewController ()
let loginPresenter: LoginPresenter = LoginPresenter(controller: loginViewController)
loginPresenter.onLoginButtonPressed(usernameText: "username", passwordText: "password")
// fulfil expectations
XCTAssertEqual(loginPresenter.didUserLogin, true)
if loginPresenter.didUserLogin {
expectation.fulfill()
}
}
// Wait for it
wait(for: [expectation], timeout: delay)

Run code after loop in background ends

I am running background thread in a while loop where I am doing some file handling task. I have some code after the loop. But the codes after the loop being executed before the loop ends (cause I am using background thread). Is there any way I can execute some code exactly after the loop ends?
Here is my code:
while i < testCount {
let task = AsyncTask(
backgroundTask: {
() -> Double in
// some file handling
return 234.09
},
afterTask: {
(val: Double) in
self.showVal(val)
}
);
task.execute();
i += 1
}
// I want to run this code after the loop ends
print("average: \(avg)")
showVal(avg)
My showVal(Double) function
func showVal(val: Double) {
print("val found: \(val)")
display.text = "\(val) found"
}
And here is my AsyncTask class
public class AsyncTask <BGParam,BGResult>{
private var pre:(()->())?;//Optional closure -> called before the backgroundTask
private var bgTask:(param:BGParam)->BGResult;//background task
private var post:((param:BGResult)->())?;//Optional closure -> called after the backgroundTask
public init(beforeTask: (()->())?=nil, backgroundTask: (param:BGParam)->BGResult, afterTask:((param:BGResult)->())?=nil){
self.pre=beforeTask;
self.bgTask=backgroundTask;
self.post=afterTask;
}
public func execute(param:BGParam){
pre?()//if beforeTask exists - invoke it before bgTask
dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), {
let bgResult=self.bgTask(param: param);//execute backgroundTask in background thread
if(self.post != nil){//if afterTask exists - invoke it in UI thread after bgTask
dispatch_async(dispatch_get_main_queue(),{self.post!(param: bgResult)});
}
});
}
}
Note: I am a beginner in Swift.
Edit:
I actually want to do:
A background file handling task
After the task ends, I want to show a text in a UILabel
I need to do this two tasks several times (say 100 times). If swift has easier methods for my purpose, please advise.
I'll try with some pseudo-code based our discussion to see if that might help you on the way. The manager-object would probably look a little something like this:
class RequestManager {
private let requiredNumberOfRequests: Int
private let completionHandler: ()->()
private var counter: Int {
didSet {
if counter == requiredNumberOfRequests {
completionHandler()
}
}
}
init(numberOfRequests: Int, completionHandler: ()->()) {
self.requiredNumberOfRequests = numberOfRequests
self.completionHandler = completionHandler
self.counter = 0
}
// Don't know exactly what you want to do here, but something like...
func performRequest(success: (value: Double)->()) {
// Network stuff, on success it looks like you'll have a Double(?)
let value: Double = 234.09
success(value: value)
counter += 1
}
}
Then, before your loop you'll create an instance of the requestManager
let manager = RequestManager(numberOfRequests: 100) { average in
print("average: \(average)")
}
...and call the performRequest as part of your loop.

How to prevent a Command Line Tool from exiting before asynchronous operation completes

In a swift 2 command line tool (main.swift), I have the following:
import Foundation
print("yay")
var request = HTTPTask()
request.GET("http://www.stackoverflow.com", parameters: nil, completionHandler: {(response: HTTPResponse) in
if let err = response.error {
print("error: \(err.localizedDescription)")
return //also notify app of failure as needed
}
if let data = response.responseObject as? NSData {
let str = NSString(data: data, encoding: NSUTF8StringEncoding)
print("response: \(str)") //prints the HTML of the page
}
})
The console shows 'yay' and then exits (Program ended with exit code: 0), seemingly without ever waiting for the request to complete. How would I prevent this from happening?
The code is using swiftHTTP
I think I might need an NSRunLoop but there is no swift example
Adding RunLoop.main.run() to the end of the file is one option. More info on another approach using a semaphore here
I realize this is an old question, but here is the solution I ended on. Using DispatchGroup.
let dispatchGroup = DispatchGroup()
for someItem in items {
dispatchGroup.enter()
doSomeAsyncWork(item: someItem) {
dispatchGroup.leave()
}
}
dispatchGroup.notify(queue: DispatchQueue.main) {
exit(EXIT_SUCCESS)
}
dispatchMain()
You can call dispatchMain() at the end of main. That runs the GCD main queue dispatcher and never returns so it will prevent the main thread from exiting. Then you just need to explicitly call exit() to exit the application when you are ready (otherwise the command line app will hang).
import Foundation
let url = URL(string:"http://www.stackoverflow.com")!
let dataTask = URLSession.shared.dataTask(with:url) { (data, response, error) in
// handle the network response
print("data=\(data)")
print("response=\(response)")
print("error=\(error)")
// explicitly exit the program after response is handled
exit(EXIT_SUCCESS)
}
dataTask.resume()
// Run GCD main dispatcher, this function never returns, call exit() elsewhere to quit the program or it will hang
dispatchMain()
Don't depend on timing.. You should try this
let sema = DispatchSemaphore(value: 0)
let url = URL(string: "https://upload.wikimedia.org/wikipedia/commons/4/4d/Cat_November_2010-1a.jpg")!
let task = URLSession.shared.dataTask(with: url) { data, response, error in
print("after image is downloaded")
// signals the process to continue
sema.signal()
}
task.resume()
// sets the process to wait
sema.wait()
If your need isn't something that requires "production level" code but some quick experiment or a tryout of a piece of code, you can do it like this :
SWIFT 3
//put at the end of your main file
RunLoop.main.run(until: Date(timeIntervalSinceNow: 15)) //will run your app for 15 seconds only
More info : https://stackoverflow.com/a/40870157/469614
Please note that you shouldn't rely on fixed execution time in your architecture.
Swift 4: RunLoop.main.run()
At the end of your file
// Step 1: Add isDone global flag
var isDone = false
// Step 2: Set isDone to true in callback
request.GET(...) {
...
isDone = true
}
// Step 3: Add waiting block at the end of code
while(!isDone) {
// run your code for 0.1 second
RunLoop.main.run(until: Date(timeIntervalSinceNow: 0.1))
}

completionHandler of AVAudioPlayerNode.scheduleFile() is called too early

I am trying to use the new AVAudioEngine in iOS 8.
It looks like the completionHandler of player.scheduleFile() is called before the sound file has finished playing.
I am using a sound file with a length of 5s -- and the println()-Message appears round about 1 second before the end of the sound.
Am I doing something wrong or do I misunderstand the idea of a completionHandler?
Thanks!
Here is some code:
class SoundHandler {
let engine:AVAudioEngine
let player:AVAudioPlayerNode
let mainMixer:AVAudioMixerNode
init() {
engine = AVAudioEngine()
player = AVAudioPlayerNode()
engine.attachNode(player)
mainMixer = engine.mainMixerNode
var error:NSError?
if !engine.startAndReturnError(&error) {
if let e = error {
println("error \(e.localizedDescription)")
}
}
engine.connect(player, to: mainMixer, format: mainMixer.outputFormatForBus(0))
}
func playSound() {
var soundUrl = NSBundle.mainBundle().URLForResource("Test", withExtension: "m4a")
var soundFile = AVAudioFile(forReading: soundUrl, error: nil)
player.scheduleFile(soundFile, atTime: nil, completionHandler: { println("Finished!") })
player.play()
}
}
I see the same behavior.
From my experimentation, I believe the callback is called once the buffer/segment/file has been "scheduled", not when it is finished playing.
Although the docs explicitly states:
"Called after the buffer has completely played or the player is stopped. May be nil."
So I think it's either a bug or incorrect documentation. No idea which
You can always compute the future time when audio playback will complete, using AVAudioTime. The current behavior is useful because it supports scheduling additional buffers/segments/files to play from the callback before the end of the current buffer/segment/file finishes, avoiding a gap in audio playback. This lets you create a simple loop player without a lot of work. Here's an example:
class Latch {
var value : Bool = true
}
func loopWholeFile(file : AVAudioFile, player : AVAudioPlayerNode) -> Latch {
let looping = Latch()
let frames = file.length
let sampleRate = file.processingFormat.sampleRate
var segmentTime : AVAudioFramePosition = 0
var segmentCompletion : AVAudioNodeCompletionHandler!
segmentCompletion = {
if looping.value {
segmentTime += frames
player.scheduleFile(file, atTime: AVAudioTime(sampleTime: segmentTime, atRate: sampleRate), completionHandler: segmentCompletion)
}
}
player.scheduleFile(file, atTime: AVAudioTime(sampleTime: segmentTime, atRate: sampleRate), completionHandler: segmentCompletion)
segmentCompletion()
player.play()
return looping
}
The code above schedules the entire file twice before calling player.play(). As each segment gets close to finishing, it schedules another whole file in the future, to avoid gaps in playback. To stop looping, you use the return value, a Latch, like this:
let looping = loopWholeFile(file, player)
sleep(1000)
looping.value = false
player.stop()
The AVAudioEngine docs from back in the iOS 8 days must have just been wrong. In the meantime, as a workaround, I noticed if you instead use scheduleBuffer:atTime:options:completionHandler: the callback is fired as expected (after playback finishes).
Example code:
AVAudioFile *file = [[AVAudioFile alloc] initForReading:_fileURL commonFormat:AVAudioPCMFormatFloat32 interleaved:NO error:nil];
AVAudioPCMBuffer *buffer = [[AVAudioPCMBuffer alloc] initWithPCMFormat:file.processingFormat frameCapacity:(AVAudioFrameCount)file.length];
[file readIntoBuffer:buffer error:&error];
[_player scheduleBuffer:buffer atTime:nil options:AVAudioPlayerNodeBufferInterrupts completionHandler:^{
// reminder: we're not on the main thread in here
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"done playing, as expected!");
});
}];
My bug report for this was closed as "works as intended," but Apple pointed me to new variations of the scheduleFile, scheduleSegment and scheduleBuffer methods in iOS 11. These add a completionCallbackType argument that you can use to specify that you want the completion callback when the playback is completed:
[self.audioUnitPlayer
scheduleSegment:self.audioUnitFile
startingFrame:sampleTime
frameCount:(int)sampleLength
atTime:0
completionCallbackType:AVAudioPlayerNodeCompletionDataPlayedBack
completionHandler:^(AVAudioPlayerNodeCompletionCallbackType callbackType) {
// do something here
}];
The documentation doesn't say anything about how this works, but I tested it and it works for me.
I've been using this workaround for iOS 8-10:
- (void)playRecording {
[self.audioUnitPlayer scheduleSegment:self.audioUnitFile startingFrame:sampleTime frameCount:(int)sampleLength atTime:0 completionHandler:^() {
float totalTime = [self recordingDuration];
float elapsedTime = [self recordingCurrentTime];
float remainingTime = totalTime - elapsedTime;
[self performSelector:#selector(doSomethingHere) withObject:nil afterDelay:remainingTime];
}];
}
- (float)recordingDuration {
float duration = duration = self.audioUnitFile.length / self.audioUnitFile.processingFormat.sampleRate;
if (isnan(duration)) {
duration = 0;
}
return duration;
}
- (float)recordingCurrentTime {
AVAudioTime *nodeTime = self.audioUnitPlayer.lastRenderTime;
AVAudioTime *playerTime = [self.audioUnitPlayer playerTimeForNodeTime:nodeTime];
AVAudioFramePosition sampleTime = playerTime.sampleTime;
if (sampleTime == 0) { return self.audioUnitLastKnownTime; } // this happens when the player isn't playing
sampleTime += self.audioUnitStartingFrame; // if we trimmed from the start, or changed the location with the location slider, the time before that point won't be included in the player time, so we have to track it ourselves and add it here
float time = sampleTime / self.audioUnitFile.processingFormat.sampleRate;
self.audioUnitLastKnownTime = time;
return time;
}
Yes, it does get called slightly before the file (or buffer) has completed. If you call [myNode stop] from within the completion handler the file (or buffer) will not fully complete. However, if you call [myEngine stop], the file (or buffer) will complete to the end
// audioFile here is our original audio
audioPlayerNode.scheduleFile(audioFile, at: nil, completionHandler: {
print("scheduleFile Complete")
var delayInSeconds: Double = 0
if let lastRenderTime = self.audioPlayerNode.lastRenderTime, let playerTime = self.audioPlayerNode.playerTime(forNodeTime: lastRenderTime) {
if let rate = rate {
delayInSeconds = Double(audioFile.length - playerTime.sampleTime) / Double(audioFile.processingFormat.sampleRate) / Double(rate!)
} else {
delayInSeconds = Double(audioFile.length - playerTime.sampleTime) / Double(audioFile.processingFormat.sampleRate)
}
}
// schedule a stop timer for when audio finishes playing
DispatchTime.executeAfter(seconds: delayInSeconds) {
audioEngine.mainMixerNode.removeTap(onBus: 0)
// Playback has completed
}
})
As of today, in a project with deployment target 12.4, on a device running 12.4.1, here's the way we found to successfully stop the nodes upon playback completion:
// audioFile and playerNode created here ...
playerNode.scheduleFile(audioFile, at: nil, completionCallbackType: .dataPlayedBack) { _ in
os_log(.debug, log: self.log, "%#", "Completing playing sound effect: \(filePath) ...")
DispatchQueue.main.async {
os_log(.debug, log: self.log, "%#", "... now actually completed: \(filePath)")
self.engine.disconnectNodeOutput(playerNode)
self.engine.detach(playerNode)
}
}
The main difference w.r.t. previous answers is to postpone node detaching on main thread (which I guess is also the audio render thread?), instead of performing that on callback thread.

Resources