The goal is to animate multiple SCNNodes at the same time then call a completion block once all the animations complete. The parallel animations have the same duration so will complete at the same time if started together.
This SO answer suggested using the group function for Sprite Kit, but there is no analog in Scene Kit because the SCNScene class lacks a runAction.
One option is run all the actions individually against each node and have each one call the same completion function, which must maintain a flag to ensure it's only called once.
Another option is to avoid the completion handler and call the completion code after a delay matched to the animation duration. This creates race conditions during testing, however, since sometimes the animations get held up before completing.
This seems clunky, though. What's the right way to group the animation of multiple nodes in SceneKit then invoke a completion handler?
The way I initially approached this was, since all the initial animations have the same duration, to apply the completion handler to just one of the actions. But, on occasion, the animations would hang-up (SCNAction completion handler awaits gesture to execute).
My current, successful solution is to not use the completion handler in conjunction with an SCNAction but with a delay:
func delay(delay:Double, closure:()->()) {
dispatch_after(
dispatch_time(
DISPATCH_TIME_NOW,
Int64(delay * Double(NSEC_PER_SEC))
),
dispatch_get_main_queue(), closure)
}
An examlpe of invocation:
delay(0.95) {
self.scaleNode_2.runAction(moveGlucoseBack)
self.fixedNode_2.runAction(moveGlucoseBack)
self.scaleNode_3.hidden = true
self.fixedNode_3.hidden = true
}
I doubt this can be called "the right way" but it works well for my uses and eliminates the random hang-ups I experienced trying to run animations on multiple nodes with completion handlers.
I haven't thought this through completely but I'll post it in hopes of being useful.
The general problem, do something after the last of a set of actions completes, is what GCD's dispatch_barrier is about. Submit all of the blocks to a private concurrent queue, then submit the Grand Finale completion block with dispatch_barrier. Grand Finale runs after all previous blocks have finished.
What I don't see right away is how to integrate these GCD calls with SceneKit calls and completion handlers.
Maybe dispatch_group is a better approach.
Edits and comments welcome!
Try something like this:
private class CountMonitor {
var completed: Int = 0
let total: Int
let then: ()->Void
init(for total: Int, then: #escaping(()->Void)) {
self.total = total
self.then = then
}
func didOne() {
completed += 1
if completed == total {
then() // Generally you should dispatch this off the main thread though
}
}
}
Then creating the actions looks something like:
private func test() {
// for context of types
let nodes: [SCNNode] = []
let complexActionsToRun: SCNAction = .fadeIn(duration: 100)
// Set up the monitor so it knows how many 'didOne' calls it should get, and what to do when they are all done ...
let monitor = CountMonitor(for: nodes.count) { () in
// do whatever you want at the end here
print("Done!")
}
for node in nodes {
node.runAction( complexActionsToRun ) { () in
monitor.didOne()
}
}
}
Note you should also account for the nodes array being empty (you might still want to do whatever you wanted to do at the end, just immediately in that case).
Related
I've been using the CloudKitShare sample code found here as a sample to help me write code for my app. I want to use performWriterBlock and performReaderBlockAndWait as found in BaseLocalCache using a completionHandler without violating the purposes of the design of the code, which focuses on being thread-safe. I include code from CloudKitShare below that are pertinent to my question. I include the comments that explain the code. I wrote comments to identify which code is mine.
I would like to be able to use an escaping completionHandler if possible. Does using an escaping completionHandler still comply with principles of thread-safe code, or does it in any way violate the purpose of the design of this sample code to be thread-safe? If I use an escaping completionHandler, I would need to consider when the completionHandler actually runs relative to other code outside of the scope of the actual perform function that uses the BaseLocalCache perform block. I would for one thing need to be aware of what other code runs in my project between the time the method executes and the time operationQueue in BaseLocalCache actually executes the block of code and thus the completionHandler.
class BaseLocalCache {
// A CloudKit task can be a single operation (CKDatabaseOperation)
// or multiple operations that you chain together.
// Provide an operation queue to get more flexibility on CloudKit operation management.
//
lazy var operationQueue: OperationQueue = OperationQueue()
// This sample ...
//
// This sample uses this dispatch queue to implement the following logics:
// - It serializes Writer blocks.
// - The reader block can be concurrent, but it needs to wait for the enqueued writer blocks to complete.
//
// To achieve that, this sample uses the following pattern:
// - Use a concurrent queue, cacheQueue.
// - Use cacheQueue.async(flags: .barrier) {} to execute writer blocks.
// - Use cacheQueue.sync(){} to execute reader blocks. The queue is concurrent,
// so reader blocks can be concurrent, unless any writer blocks are in the way.
// Note that Writer blocks block the reader, so they need to be as small as possible.
//
private lazy var cacheQueue: DispatchQueue = {
return DispatchQueue(label: "LocalCache", attributes: .concurrent)
}()
func performWriterBlock(_ writerBlock: #escaping () -> Void) {
cacheQueue.async(flags: .barrier) {
writerBlock()
}
}
func performReaderBlockAndWait<T>(_ readerBlock: () -> T) -> T {
return cacheQueue.sync {
return readerBlock()
}
}
}
final class TopicLocalCache: BaseLocalCache {
private var serverChangeToken: CKServerChangeToken?
func setServerChangeToken(newToken: CKServerChangeToken?) {
performWriterBlock { self.serverChangeToken = newToken }
}
func getServerChangeToken() -> CKServerChangeToken? {
return performReaderBlockAndWait { return self.serverChangeToken }
}
// Trial: How to use escaping completionHandler? with a performWriterBlock
func setServerChangeToken(newToken: CKServerChangeToken?, completionHandler: #escaping (Result<Void, Error>)->Void) {
performWriterBlock {
self.serverChangeToken = newToken
completionHandler(.success(Void()))
}
}
// Trial: How to use escaping completionHandler? with a performReaderBlockAndWait
func getServerChangeToken(completionHandler: (Result<CKServerChangeToken, Error>)->Void) {
performReaderBlockAndWait {
if let serverChangeToken = self.serverChangeToken {
completionHandler(.success(serverChangeToken))
} else {
completionHandler(.failure(NSError(domain: "nil CKServerChangeToken", code: 0)))
}
}
}
}
You asked:
Does using an escaping completionHandler still comply with principles of thread-safe code, or does it in any way violate the purpose of the design of this sample code to be thread-safe?
An escaping completion handler does not violate thread-safety.
That having been said, it does not ensure thread-safety, either. Thread-safety is solely a question of whether you ever access some shared resource from one thread while mutating it from another.
If I use an escaping completionHandler, I would need to consider when the completionHandler actually runs relative to other code outside of the scope of the actual perform function that uses the BaseLocalCache perform block.
Yes, you need to be aware that the escaping completion handler is called asynchronously (i.e., later). That is less of a thread-safety concern than a general understanding of the application flow. It is only a question of what you might be doing in that closure.
IMHO, the more important observation is that the completion handler is called on the cacheQueue used internally by BaseLocalCache. So, the caller needs to be aware that the closure is not called on the caller’s current queue, but on cacheQueue.
It should be noted that elsewhere in that project, they employ another common pattern, where the completion handler is dispatched back to a particular queue, e.g., the main queue.
Bottom line, thread-safety is not a question of whether a closure is escaping or not, but rather (a) from what thread does the method call the closure; and (b) what the supplied closure actually does:
Do you interact with the UI? Then you will want to ensure that you dispatch that back to the main queue.
Do you interact with your own properties? Then you will want to make sure you synchronize all of your access with them, either with actors, relying on the main queue, use your own serial queues, or a reader-writer pattern like in the example you shared with us.
If you are ever unsure about your code’s thread-safety, you might consider temporarily turning on TSAN as described in Diagnosing Memory, Thread, and Crash Issues Early
Let me show a simplified example of the problem I'm struggling with:
class CarService {
func getCars() -> Single<[Car]> {
return Single.create { observer in
// Here we're using a thread that was defined in subscribeOn().
someCallbackToAPI { cars in
// Here we're using main thread, because of the someCallbackToAPI implementation.
observer(.success(cars))
}
}
}
}
class CarRepository {
func syncCars() -> Completable {
return CarService().getCars()
.flatMapCompletable { cars in
// Here we're using main thread, but we want some background thread.
saveCars(cars)
}
}
}
class CarViewController {
func loadCar() {
CarRepository().syncCars()
.subscribeOn(someBackgroundScheduler)
.observeOn(MainThread)
.subscribe()
}
}
From the bottom: CarViewController wants to sync all the cars from some external API. It defines what thread should be used for the sync with subscribeOn - we don't want to block the UI thread. Unfortunately, underneath, the CarService has to use some external library methods (someCallbackToAPI) that always returns the result in a main thread. The problem is that after receiving the result, all methods below like e.g. saveCars are called in the same main thread. saveCars may block the UI thread because it saves data to database. Of course I could add observeOn between threads between CarService().getCars() and flatMapCompletable, but I want the CarRepository to be dump and know nothing about the threads. It is the CarViewController responsibility to define working thread.
So my question is, is it a way I could get the scheduler passed in subscribeOn method and switch back to the scheduler after receiving the result from someCallbackToApi?
The short answer is no.
As you surmise, the problem is that your someCallbackToAPI is routing to the main thread which is not what you wanted and there's nothing you can do about that short of re-writing someCallbackToAPI. If you are using Alamofire or Moya, I think they have alternative methods that won't call the closure on the main thread but I'm not sure. URLSession does not switch to the main thread so one idea would be to use it instead.
If you want the saveCars to happen on a background thread, you will have to use observeOn to push the computation back onto a background thread from main. The only thing subscribeOn will do is call someCallbackToAPI(_:) on a background thread, it cannot dictate what thread the function will call its closure on.
So something like:
func syncCars() -> Completable {
return CarService().getCars()
.observeOn(someBackgroundScheduler)
.flatMapCompletable { cars in
// Now this will be on the background thread.
saveCars(cars)
}
}
As a final note, an empty subscribe is a code smell. Any time you find your-self calling .subscribe() for anything other than testing purposes, you are likely doing something wrong.
I have a function that I want only to execute if not executing currently.
I have used a bool variable to check the current execution.
Is there any other solution provided by Swift to handle this instead of using Bool?
guard
!isExecuting,
let currentNavVC = tabBarController.selectedViewController as? UINavigationController
else { return }
isExecuting = true
let first = currentNavVC.viewControllers.first,
let last = currentNavVC.viewControllers.last
var controllers = [first]
if first != last {
controllers = [first, last]
}
DispatchQueue.main.async {
currentNavVC.viewControllers = controllers
isExecuting = false
}
Bool variable: isExecuting
Note:
Tried using Semaphores(DispatchSemaphore) but they are of no help.
Also I am calling the above function in didReceiveMemoryWarning()
Any help will be appreciated and thanks in advance!!
I have a function that I want only to execute if not executing currently
You're looking for a lock. But locks of themselves are tricky and dangerous. The easy, safe way to get a lock is to use a serial queue. As we say, a serial queue is a form of lock. So:
If your function is called on the main queue, then it cannot execute if it is executing currently, and there is nothing to do. The main queue is a serial queue and there can be Only One.
If your function is called on a background queue, then make sure that your queue is a serial queue. For example, if you create your own DispatchQueue, it is serial by default.
I believe you also can use Operation with OperationQueue in this case.
Operation supports cancellation as well as checking if it is executing.
Ref:
OperationQueue: https://developer.apple.com/documentation/foundation/operationqueue
Operation: https://developer.apple.com/documentation/foundation/operation
I have the following async recursive code:
func syncData() {
dal.getList(...) { [unowned self] list, error in
if let objects = list {
if oneTime {
oneTime = false
syncOtherStuffNow()
}
syncData() // recurse until all data synced
} else if let error = error {... }
func syncOtherStuffNow() { } // with its own recursion
My understanding is that the recursion will build the call stack until all the function calls complete, at which point they will all unwind and free up the heap.
I also want to trigger another function (syncOtherStuffNow) from within the closure. But don't want to bind it to the closure with a strong reference waiting for it's return (even though it's async too).
How can I essentially trigger the syncOtherStuffNow() selector to run, and not affect the current closure with hanging on to its return call?
I thought of using Notifications, but that seems overkill given the two functions are in the same class.
Since dal.getList() takes a callback I guess it is asynchronous and so the the first syncData starts the async call and then returns immediately which lets syncData() return.
If syncOtherStuffNow() is async it will return immediately and so dataSync() will not wait on it finishing its job and so continue with its execution to the end.
You can test whether sth builds a callstack by putting a breakpoint on every recursion and look on the callstack how many calls of the same function are ontop.
What I do is recurse with asyncAfter, which unwinds the call stack.
I have 2 delegate methods that are being called by notifications from a 3rd party library.
Method 1:
mediaContentWasUpdated()
Method 2:
adMediaDidBeginPlaying()
In Method 1, a key variable (adDuration) is set from a parameter that is passed in with the notification. As far as I can see this is the only place to get this information.
In Method 2, we check the adDuration and if it is greater than 0 then we update the UI to reflect that we are in fact play an ad.
A bug has appeared where sometimes these two methods are called in the wrong order. Meaning the adDuration is not set and Method 2 thinks there is no ad media to be played and does not update the UI accordingly.
My current attempt at a solution is to make adDuration optional and use an NSCondition to cause Method 2 to wait for Method 1 to set adDuration and then proceed.
var adDuration : Double?
let condition = NSCondition()
func mediaContentWasUpdated(notification: NSNotificiation) {
condition.lock()
if(notificationHasAdDurationInfo(notification)) {
self.adDuration = getAdDuration(notification)
condition.signal()
}
condition.unlock()
}
func adMediaDidBeginPlaying(notification: NSNotification) {
condition.lock()
while adDuration == nil {
condition.wait()
}
if adDuration! > Double(0) {
updateUIForAd()
}
condition.unlock()
}
This is my first time trying something like this and I worry I am doing something wrong. I also have some concerns about locking and unlocking threads needlessly (which would happen in a well timed run, or if there were no ad content to be played).
Outside factors are hindering my ability to test and I wanted to get some input to see if I am heading in the right direction while I wait for those issues to be resolved.
Your discussion of NSCondition got me on the same track with you, and I built two or three solutions using DispatchGroup (which is the better tool for this), but they always had little corner cases that could behave badly, and didn't really capture the intent.
(If you're interested in the DispatchGroup solutions, they're of the form: call .enter() in init, call .leave() when the duration comes in, call notify() when the playing starts. It works fine, but it introduces corner cases that can crash, just like NSCondition.)
Getting back to the real intent:
Update the UI when the duration is known and the ad has started playing.
There's no concurrency going on here. So pulling out GCD is not just overkill; it actually makes things worse because it introduces lots of complicated corner cases.
So I thought about how I'd have solved this back before GCD. And the answer is obvious: just check if you have the data you want, and then do the thing. (Reading through the comments, I see Paulw11 pointed this out as well.)
Personally I like to pull this kind of thing into its own type to make things more self-contained. I hate some of the names here, but the idea should be clear:
class AdPlayer {
private var readyToPlay = false
private var duration: Double = 0.0
private let completion: (Double) -> Void
func setDuration(from notification: Notification) {
if(notificationHasAdDurationInfo(notification)) {
duration = getAdDuration(notification)
}
playIfReady()
}
func play() {
readyToPlay = true
playIfReady()
}
private func playIfReady() {
if duration > 0 && readyToPlay {
completion(duration)
}
}
init(completion: #escaping (Double) -> Void) {
self.completion = completion
}
}
When you set each thing, see if you're ready to update, and if so, update. I've gotten rid of the optional as well, since I believe the intent is "0 duration is always wrong." But you could use an Optional so you could detect actually receiving a 0 from the notification.
With that, you just set up a player property:
player = AdPlayer(completion: updateUIForAd)
(Note that the above might be creating a retain loop, depending on what updateUIForAd is; you may need a [weak self] closure or the like here.)
And then update it as needed:
func mediaContentWasUpdated(notification: NSNotificiation) {
player.setDuration(from: notification)
}
func adMediaDidBeginPlaying(notification: NSNotification) {
player.play()
}
A big advantage of creating the AdPlayer type is that it's easy to reset the system when the ad is done (or if something goes wrong). Just throw away the whole object and create another one.