Game sometimes crashes when removing CADisplayLink from run loop - ios

This doesn't happen every time you play the game, maybe once for every 5 or 10 plays. When the game ends, I remove my CADisplayLink (which I use to animate the playing area, a bit like the pipes in Flappy Bird) from the run loop. However, on the few occasions, it crashes on that line. Next to the line, it has:
Thread 1: EXC_BAD_ACCESS (code=1, address=0x10)
This is the code:
func endGame(r : String) {
UIView.animateWithDuration(0.4, delay: 0.2, options: .CurveLinear, animations: {
self.scoreLabel.alpha = 0
}, completion: {
(finished: Bool) in
self.scoreLabel.removeFromSuperview()
});
self.view.userInteractionEnabled = false
reason = r
println("Game Over!!!")
//Crashes on this line
blockUpdateDisplayLink.removeFromRunLoop(NSRunLoop.currentRunLoop(), forMode: NSRunLoopCommonModes)
shiftDisplayLink.removeFromRunLoop(NSRunLoop.currentRunLoop(), forMode: NSRunLoopCommonModes)
scoreTimer.invalidate()
UIView.animateWithDuration(0.0001, delay: 0.7, options: .CurveLinear, animations: {
}, completion: {
(finished: Bool) in
self.performSegueWithIdentifier("Game Over", sender: self)
});
}
if I comment out the first CADisplayLink part, it will just crash on the second anyway.
This is the stacktrace:
Which has the same "Thread 1" error as above.
What is going on??

You must invalidate display links (rather than just removing them from the run loop) because if they are in the middle of running when they activate they may still try to use their associated run loop.

Which run loop are you adding the CADisplayLink to? You might need to be using NSRunLoop.mainRunLoop() instead.
Also, if your CADisplayLink is only being added on one NSRunLoop, you could try calling blockUpdateDisplayLink.invalidate() instead of removing it.
If you post the code where you create the CADisplayLink objects, it will make it easier to track down the issue.

I will really suggest you to debug you're code with Instruments and NSZombies and find out your memory issue problem.
Instruments
NSZombies

Related

How to play a lottie animation once then loop from frame to frame in iOS/Swift/Xcode?

I'm looking for a solution to play one part of my animation once but the other half on loop while using Lottie Files in Xcode. I think there is method in JS method playSegments() which can solve this problem but I would like to code this in swift.
I found a similar problem, but for JS: https://github.com/airbnb/lottie-web/issues/579
This is what I tried but it's not working as I expected:
Have you tried to play the second part after the completion of the first one?
animationView.play(fromProgress: 0,
toProgress: 1,
loopMode: LottieLoopMode.playOnce,
completion: { (finished) in
if finished {
print("Animation Complete, Start second one")
} else {
print("Animation cancelled")
}
})

How to update UI after delay in swift

DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.5) { animations() }
I make changes with UI (in main thread) and I need to delay further animations for a while and then execute them (synchronously). Code above works, but with "This application is modifying the autolayout engine from a background thread after the engine was accessed from the main thread. This can lead to engine corruption and weird crashes" error.
Thanks for help!
You can make a delay like this
UIView.animate(withDuration: 0.8, delay: 0.5, options: [], animations: {
/// animations here
}, completion: nil)

How to turn on iPhone's flash for 1 second?

In the settings for my app, there is a switch allowing the user to turn iPhone's flash on or off (flash is used to indicate certain points in app logic while it's running). What I want to implement is this: when the user toggles this switch on, I want it to, well, flash for a split second to indicate its 'ON' state.
Now, I know how to set torchMode on or off - this is implemented in the app itself, but I'm not sure how to correctly make it 'blink' for settings purpose. One of the ways I thought of is to use following code (toggleFlash() is a static method for toggling torchMode implemented in main code):
UIView.animate(withDuration: 1.0, animations: {
ViewController.toggleFlash(on: true)
}, completion: { (_) in
ViewController.toggleFlash(on: false)
})
This does make it 'blink', but only for a moment - not 1 second. Besides, I'm not so sure if it's at all correct to use animate for this purpose. Another idea is to use Thread.sleep, but this looks like an even worse practice.
Can someone recommend better solutions?
You could use a timer.
func flashForOneSecond() {
ViewController.toggleFlash(on: true)
flashOffTimer = Timer.scheduledTimer(timeInterval:1, target:self, selector:#selector(self.switchFlashOff), userInfo:nil, repeats:false)
}
#objc func switchFlashOff() {
ViewController.toggleFlash(on: false)
}
Probably something like this:
func flash() {
ViewController.toggleFlash(on: true)
let time = DispatchWallTime.now() + DispatchTimeInterval.seconds(1)
DispatchQueue.main.asyncAfter(wallDeadline: time) {
ViewController.toggleFlash(on: false)
}
}
wallDeadline is reliable and the solution is packed in one function.

iOS Timer function heap corruption

So I am new to iOS and am using trying to have a view controller with a timer that periodically updates the UI. The issue that I am seeing is that I am getting heap corruption, more specifically EXC_BAD_ACCESS KERN_INVALID_ADDRESS error that is caused by objc_retain call.
This error is happening in several places but all within my Timer function and higher on the call stack __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION
is being called in each case.
I must be missing a reference or not releasing something properly, here is the code
func scheduleTimer() {
timer = Timer.scheduledTimer(timeInterval: timeInterval, target: self, selector: #selector(self.timerFunc), userInfo: nil, repeats: true)
}
func timerFunc() {
if let gps = sdlService?.getLatestLocation() {
let clCoor = CLLocationCoordinate2D(locStruct: gps)
self.updateLatestDriverIcon(gps: gps, coor: clCoor)
if isRecording {
self.addNextPathPoint(coor: clCoor)
}
latestCoor = clCoor
}
}
func updateLatestDriverIcon(gps: LocationStruct, coor: CLLocationCoordinate2D) {
if latestCoor == nil {
car = MarkerAnnotation(coordinate: coor, title: carMarker)
mapView.addAnnotation(car!)
latestCoor = coor
mapView.centerOnLatestGPS(animated: false)
markerView.rotation = MathUtils.wrap(gps.bearing, min: 0, max: 360)
} else if coor.isDifferent(to: latestCoor!) {
if isMapFollowingCar {
mapView.centerOnLatestGPS(animated: false)
}
car!.coordinate = coor
markerView.rotation = MathUtils.wrap(gps.bearing, min: 0, max: 360)
}
}
Now this timer function is referencing properties of my view controller, as well as a nested function (updateLatestDriverIcon). I have seen crashes on the mapView.centerOnLatestGPS() func, and multiple places within the markerView.rotation call stack all with the same error codes listed above.
What am I missing here?
EDIT:
Here is a stack trace from crashlytics. I am using events triggered over an external accessory so I can be attached to the debugger:
Stack Trace
So after several weeks of tracking this thing down, we found it was due to an animation on a UIView. Not exactly sure why it was throwing errors where it did, if anyone knows why that would be very helpful! Here is some more info on the architecture:
We had a screen updating a UI at about 10HZ and was driven by a timer using the above code. The animation was done on a UIView subclass that was done off of the main thread which was being rendered into a bitmap context. This was being done at ~30Hz.
The animation code:
UIView.animate(
withDuration: self.animationDuration,
animations: { self.currentGearValue = actualGearValue },
completion: { (isComplete) in /* not sure we need this yet */ })
I haven't tested it but it might be because the animation is overlapped if the previous one isn't finished by the time the next animation gets started.

Completion block in (animateWithDuration:animations:completion:) is unpredictably delayed

The code is too huge to post it here. My problem is the following. When I call animateWithDuration:animations:completion: (maybe with options) with duration == 0.3 it doesn't mean that the completion block will be called through the same delay. It is called through 2 seconds instead and it is too long for me.
This big delay usually appears before memory warnings but sometimes may work as expected.
Could anybody explain what may cause such a strange behaviour?
Are there any timers involved, like is the animation timer-triggered?
I had a similar problem when my animation was timer-triggered. It turned out the animation was started more than once. animationOngoing flag stopped animation from being started again before finishing.
// Timer function
func timerTextToggle(timer: NSTimer) {
if self.animationOngoing == false {
self.flipAnimation()
}
}
// Animation function
func flipAnimation() {
// important note: it's UIViewAnimationOptions,
// not UIViewAnimationTransition
self.animationOngoing = true
if self.animationToggle == false {
UIView.transitionFromView(self.singleTapLabel!,
toView: self.doubleTapLabel!,
duration: animDuration,
options: UIViewAnimationOptions.TransitionFlipFromBottom,
completion: {
(value: Bool) in
self.animationOngoing = false
})
} else {
UIView.transitionFromView(self.doubleTapLabel!,
toView: self.singleTapLabel!,
duration: animDuration,
options: UIViewAnimationOptions.TransitionFlipFromTop,
completion: {
(value: Bool) in
self.animationOngoing = false
})
}
self.animationToggle = !self.animationToggle
}
I experienced a similar problem to this, although without further information on your scenario, I don't know if this also applies to your issue.
I was calling becomeFirstResponder on a UITextField in the completion block of my animateWithDuration:delay:options:animations:completion. Logging showed the completion block was being called in a timely manner, but the keyboard was taking several seconds to show. This was only occurring on first launch of the keyboard.
This answer helped me solve this... turned out this was somehow linked to the iOS Simulator. This issue did not occur when I wasn't debugging the app... another classic example of chasing a bug for hours in the simulator that didn't actually exist.
The cause of this problem is found out. It is a lot of UIWebView objects rendered in the main thread. And it seems impossible to prevent their loading. Anyways time profiler show that a lot of time is spent to render them even if they are not visible on the screen.
And yes, I can't release them before memory warning event because of requirements

Resources