In iOS AVPlayer, addPeriodicTimeObserverForInterval seems to be missing - ios

I'm trying to setup AVPlayer.addPeriodicTimeObserverForInterval(). Has anyone used this successfully?
I'm using Xcode 8.1, Swift 3

The accepted answer makes it feel like that you can assign the return value to a local variable and ignore it. But according to the doc, it is actually important the hold on strong reference to the return value and removeTimeObserver(_ :).
You must maintain a strong reference the returned value as long as you want the time observer to be invoked by the player. Each invocation of this method should be paired with a corresponding call to
removeTimeObserver(:)
. Releasing the observer object without invoking
removeTimeObserver(:)
will result in undefined behaviour.
So I would do:
if let ob = self.observer {
player.removeTimeObserver(ob)
}
let interval = CMTimeMake(1, 4) // 0.25 (1/4) seconds
self.observer = player.addPeriodicTimeObserver(forInterval: interval, queue: DispatchQueue.main) { [weak self] time in
...
}

Check this func addPeriodicTimeObserver(forInterval interval: CMTime,
queue: DispatchQueue?,
using block: #escaping (CMTime) -> Void) -> Any
It is in the documents also for example check this code snippet
let timeObserverToken = player.addPeriodicTimeObserver(forInterval: interval, queue: DispatchQueue.main) { [unowned self] time in
}
Referenced from here

Related

Trying to wipe out pasteboard after time interval

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.

Weak DispatchGroup in closures and other GCD questions

Swift closures strongly capture reference types.
DispatchGroup is a reference type.
My questions have to do with the following code:
func getUsername(onDone: #escaping (_ possUsername: String?) -> ())
{
//Post request for username that calls onDone(retrievedUsername)...
}
func getBirthdate(using username: String?, onDone: #escaping (_ possBday: String?) -> ())
{
//Post request for token that calls onDone(retrievedToken)...
}
func asyncTasksInOrder(onDone: #escaping (_ resultBDay: String?) -> ())
{
let thread = DispatchQueue(label: "my thread", qos: .userInteractive, attributes: [],
autoreleaseFrequency: .workItem, target: nil)
thread.async { [weak self, onDone] in
guard let self = self else {
onDone(nil)
return
}
let dg = DispatchGroup() //This is a reference type
var retrievedUsername: String?
var retrievedBday: String?
//Get username async first
dg.enter()
self.getUsername(onDone: {[weak dg](possUsername) in
retrievedUsername = possUsername
dg?.leave() //DG is weak here
})
dg.wait()
//Now that we've waited for the username, get bday async now
dg.enter()
self.getBirthdate(using: retrievedUsername, onDone: {[weak dg](possBday) in
retrievedBday = possBday
dg?.leave() //DG is also weak here
})
dg.wait()
//We've waited for everything, so now call the return callback
onDone(retrievedBday)
}
}
So the two closures inside of asyncTasksInOrder(onDone:) each capture dg, my DispatchGroup.
Is it even necessary to capture my dispatch group?
If I don't capture it, how would I even know I've got a retain cycle?
What if the dispatch group evaporates during one of the callback executions? Would it even evaporate since it's waiting?
Is it unnecessarily expensive to instantiate a DispatchQueue like this often (disregarding the .userInteractive)? I'm asking this particular question because spinning up threads in Android is extremely expensive (so expensive that JetBrains has dedicated lots of resources towards Kotlin coroutines).
How does dg.notify(...) play into all of this? Why even have a notify method when dg.wait() does the same thing?
I feel like my understanding of GCD is not bulletproof, so I'm asking to build some confidence. Please critique as well if there's anything to critique. The help is truly appreciated.
1) No, the dispatch group is captured implicitly. You don't even need to capture self in async because GCD closures don't cause retain cycles.
2) There is no retain cycle.
3) Actually you are misusing DispatchGroup to force an asynchronous task to become synchronous.
4) No, GCD is pretty lightweight.
5) The main purpose of DispatchGroup is to notify when all asynchronous tasks – for example in a repeat loop – are completed regardless of the order.
A better solution is to nest the asynchronous tasks. With only two tasks the pyramid of doom is manageable.
func asyncTasksInOrder(onDone: #escaping (String?) -> Void)
{
let thread = DispatchQueue(label: "my thread", qos: .userInteractive, autoreleaseFrequency: .workItem)
thread.async {
//Get username async first
self.getUsername { [weak self] possUsername in
guard let self = self else { onDone(nil); return }
//Now get bday async
self.getBirthdate(using: possUsername) { possBday in
//Now call the return callback
onDone(possBday)
}
}
}
}

How to call method when AVAudioPlayer reaches a certain playback time?

I'm trying to perform an action when the playback reaches a certain time. I can't find any delegate methods or examples of how to do this. How can I call a method when the playback reaches a certain point?
That should help you addBoundaryTimeObserverForTimes:queue:usingBlock:
Requests the invocation of a block when specified times are traversed
during normal playback.
Objective C:
- (id)addBoundaryTimeObserverForTimes:(NSArray<NSValue *> *)times
queue:(dispatch_queue_t)queue
usingBlock:(void (^)(void))block;
Swift:
func addBoundaryTimeObserver(forTimes times: [NSValue],
queue: DispatchQueue?,
using block: #escaping () -> Void) -> Any
Usage:
_ = self.player.addBoundaryTimeObserver(forTimes: times, queue: DispatchQueue.main, using: {
[weak self] time in
// Your code goes here
})
Adding on some more details.
You can addBoundaryTimeObserver(forTimes:queue:using:) for a certain time or use addPeriodicTimeObserver(forInterval:queue:using:) for periodic intervals.
// for specific time
func addTimeObserver() {
var times = [NSValue]()
var currentTime = kCMTimeZero // make your time here
times.append(NSValue(time:currentTime))
// Queue on which to invoke the callback
let mainQueue = DispatchQueue.main
// Add time observer
timeObserverToken =
player.addBoundaryTimeObserver(forTimes: times, queue: mainQueue) {
[weak self] time in
// Update UI
}
}
To fire every half second during normal playback
func addPeriodicTimeObserver() {
// Invoke callback every half second
let interval = CMTime(seconds: 0.5,
preferredTimescale: CMTimeScale(NSEC_PER_SEC))
let mainQueue = DispatchQueue.main
timeObserverToken =
player.addPeriodicTimeObserver(forInterval: interval, queue: mainQueue) {
[weak self] time in
// update UI
}}

ReactiveSwift: How to write a Task Scheduler

I am trying to create a scheduler to consume some data.
The scheduler will have to be able to:
send an event each time data should be consumed manually
send an event each time data should be consumed automatically after some time has elapsed after the last consumption
I have modelled the manual consumption with a MutableProperty
let consume = MutableProperty<Void>()
and I am trying to model the automatic consumption with a SignalProducer
let timer = SignalProducer<Void, NoError>
I can get the first time that I need to consume that data by combining the latest values of these two producers like
SignalProducer.combineLatest(consume.producer, timer)
.take(first: 1)
.map() { _ in return () }
That way whichever comes first, a manual consumption or an automatic one the producer will send a value.
I can't figure out how I will be able to do this perpetually.
Can someone help?
You can start a timer using the global timer functions defined in ReactiveSwift
public func timer(interval: TimeInterval, on scheduler: DateSchedulerProtocol) -> SignalProducer<Date, NoError>
To combine the timer with the consume property:
let interval = 10.0
let timerSignal: SignalProducer<Date, NoError> = timer(interval: interval, on: QueueScheduler.main)
let consume = MutableProperty<Void>()
timerSignal.combineLatest(with: consume.producer).startWithValues { (date, v) in
print("triggered at time: \(date)")
}
This way you can trigger the print block manually by setting the value property on consume, or by waiting for the timer event.
You can wrap timer into SignalProducer
func timerProducer() -> SignalProducer<Void, Never> {
return SignalProducer<Void, Never> { observer, disposable in
let timer = Timer(timeInterval: 10, repeats: true) { timer in
if disposable.hasEnded {
timer.invalidate()
} else {
observer.send(value: ())
}
}
RunLoop.main.add(timer, forMode: .common)
}
}
Note: If you starting this producer from NON-main thread, you need add it to RunLoop. Otherwise it will not get triggered.

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