I am making my SpriteKit game. When the player dies, my goal is to have the game transition back to the start screen. This is accomplished by the code below. However, I notice that the memory increases each time a new game begins. Xcode Instruments is not showing a memory leak. When the memory reaches roughly 150mb the games frame rate drops and the game become unplayable.
In the GameScene I call this function when the player dies
func gameOver(){
if let block = gameOverBlock {
worldNode.removeAllChildren()
worldNode.removeAllActions()
worldNode.removeFromParent()
self.removeAllChildren()
block()
}
}
Back in the GameViewController the following functions get called
scene!.gameOverBlock = {
[weak self] in
self!.goBack()
}
}
func goBack(){
scene!.removeFromParent()
navigationController!.popToRootViewControllerAnimated(false)
return
}
If anyone has any ideas as to how I can accomplish this without a memory leak, it would much be appreciated.
After commenting out tons of code, I have found the problem. The methods that I have posted above were not causing the leak, as Matthew suggested, there was a strong reference in the middle of my code that was stopping the ARC from releasing memory. Ill post the problem code incase anyone else may have a similar problem.
In my GameViewController, I had the following block:
scene!.zoomInBlock = {
self.scene!.size = CGSizeMake(self.scene!.size.width / 2, self.scene!.size.height / 2)
}
The correct way (without causing a strong reference) to write this would be:
scene!.zoomInBlock = {
[unowned self] in self.scene!.size = CGSizeMake(self.scene!.size.width / 2, self.scene!.size.height / 2)
}
Related
In a SceneKit project, the following method is intermittently (but consistently) crashing with EXC_BAD_ACCESS. Specifically, it says Thread 1: EXC_BAD_ACCESS (code=1, address=0x0).
contactTestBetween(_:_:options:)
The method is called from inside SceneKit's SCNSceneRendererDelegate method. It's also being run on the main thread because otherwise, this code crashes even more often. So, here's the greater context:
func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
var ball = self.scene?.rootNode.childNode(withName: "ball", recursively: true)
var ballToFloorContact: [SCNPhysicsContact]?
let theNodes: [SCNNode]? = self.scene?.rootNode.childNodes.filter({ $0.name?.contains("floor") == true})
let optionalWorld: SCNPhysicsWorld? = self.scene?.physicsWorld
DispatchQueue.main.async {
if let allNodes = theNodes {
for i in 0..<allNodes.count {
let n = allNodes[i]
if let b = n.physicsBody, let s = ball?.physicsBody {
ballToFloorContact = optionalWorld?.contactTestBetween(b, s)
}
}
}
}
}
The SCNSceneRendererDelegate is set in viewDidLoad:
scnView.delegate = scnView
Additional info:
When the crash occurs, optionalWorld, b, and s are all properly defined.
I originally had the call to filter located inside the DispatchQueue, but it was causing a crash that seemed identical to this one. Moving that line outside the DispatchQueue solved that problem.
Question: Any idea what might be causing this crash, and how I might avoid it? Am I doing something wrong, here?
Thanks!
UPDATE: I tried adding the following guard statement to protect against a situation where the contactTestBetween method is, itself, nil (after all, that seems to be what Xcode is telling me):
guard let optionalContactMethod = optionalWorld?.contactTestBetween else {
return
}
However, after some additional testing time, contactTestBetween eventually crashed once again with EXC_BAD_ACCESS on the line guard let optionalContactMethod = optionalWorld?.contactTestBetween else {. I truly do not understand how that could be, but it be. Note that I tried this guard paradigm both with and without the presence of the DispatchQueue.main.async call, with the same result.
I did two things, here:
I added Accelerometer and Gyroscope to the UIRequiredDeviceCapabilities key in my Info.plist file. I did this because my game uses Core Motion, but I had neglected to include the necessary values.
On a hunch, I replaced the SCNSceneRendererDelegate method renderer(_: SCNSceneRenderer, updateAtTime: TimeInterval) with the alternative method renderer(_: SCNSceneRenderer, didRenderScene: SCNScene, atTime: TimeInterval).
Since doing these things, I haven't been able to reproduce a crash.
An alternative unsafe fix is
try! await Task.sleep(nanoseconds: 1)
somewhere before invoking contactTestBetween().
This answer sits here as a warning to anyone who might want to use async/await as a fix or is inadvertendly using some await (any kind of await works) to break out of the run loop which somehow makes debugging EXC_BAD_ACCESS nearly impossible because it will get triggered rarely enough.
I know from experience that if the above fixed a race then races WILL still happen especially and mostly with CPUs running at 100% and actually I got contactTestBetween to crash once after many tries.
I have no clue what's going on behind the scenes or how to synchronize with physicsWorld.
#West1 solution is still the best if it works as advertised.
I have a view controller that uses CoreMotion to monitor the device's Attitude.
Here is the handler that is used in the call to startDeviceMotionUpdates():
/**
* Receives device motion updates from Core Motion and uses the attitude of the device to update
* the position of the attitude tracker inside the bubble level view.
*/
private func handleAttitude(deviceMotion: CMDeviceMotion?, error: Error?) {
guard let attitude = deviceMotion?.attitude else {
GLog.Error(message: "Could not get device attitude.")
return
}
// Calculate the current attitude vector
let roll = attitude.roll
let pitch = attitude.pitch - optimalAngle
let magnitude = sqrt(roll * roll + pitch * pitch)
// Drawing can only happen on the main thread
DispatchQueue.main.async {
[weak self] in
guard let weakSelf = self else {
GLog.Log("could not get weak self")
return
}
// Move the bubble in the attitude tracker to match the current attitude
weakSelf.bubblePosX.constant = CGFloat(roll * weakSelf.attitudeScalar)
weakSelf.bubblePosY.constant = CGFloat(pitch * weakSelf.attitudeScalar)
// Set the border color based on the current attitude.
if magnitude < weakSelf.yellowThreshold {
weakSelf.attitudeView.layer.borderColor = weakSelf.green.cgColor
} else if magnitude < weakSelf.redThreshold {
weakSelf.attitudeView.layer.borderColor = weakSelf.yellow.cgColor
} else {
weakSelf.attitudeView.layer.borderColor = weakSelf.red.cgColor
}
// Do the actual drawing
weakSelf.view.layoutIfNeeded()
}
}
I added [weak self] to see if it would fix things, but it has not. This crash is not easy to reproduce.
When I am done the with VC that uses CoreMotion, I call stopDeviceMotionUpdates() in the VC's viewWillDisappear() method. This VC is the only class in the app that imports CoreMotion.
However, when I arrive in the next VC, occasionally I see EXC_BAD_ACCESS getting thrown on a co m.apple.CoreMotion.MotionThread.
Anybody know why CoreMotion would spawn a thread after the VC that used it has been dismissed? I've verified that the VC is no longer in memory when the crash happens. And yes, the two VCs I'm dealing with are presented modally.
I've examined the memory graph, and when the crash happens, these CoreMotion objects are being reported:
And:
I don't know if those objects should still be in memory after the instance of the CoreMotionManager has been deallocated or not. According to the memory graph, there is no instance of CoreMotionManager in memory.
The VC that imports CoreMotion also imports ARKit. Not sure if some crazy interaction between CoreMotion and ARKit is the problem.
There does seem to be something going on between the main thread and the MotionThread(14):
I'm not sure what to make of the main thread stack trace.
Sometimes when the CoreMotion VC is dismissed, I've noticed that there is a lag in the memory it uses getting released. The release always happens eventually.
Thanks to anybody who can help!
We have a ARSCNView member. We were not calling sceneView.session.pause() when we dismissed the VC that used the sceneView member. One line of code. That was it.
Are you passing the function handleAttitude direct to startDeviceMotionUpdates
as in startDeviceMotionUpdates(to:someQueue, withHandler:handleAttitude)
That will set up a retain cycle between your VC and CMMotionManager
Try
singletonMM.startDeviceMotionUpdates(to:someQueue) { [weak self] motion,error in
self?.handleAttitude(motion,error)
}
To prevent a strong ref to your VC.
I have the following method that is called when the delete button is pressed in an IOS keyboard app extension:
func delete()
{
dispatch_async(dispatch_get_main_queue(),
{
for _ in 1..<50
{
(self.textDocumentProxy as UIKeyInput).deleteBackward()
}
print("Deletion End")
})
}
However, each time this method is called my keyboard's memory usage goes up by 1-2mb and it does not come back down. This quickly results in a crash.
I am not allocating or de-allocating any objects in this thread, so I am not sure why there is a massive memory leak.
UPDATE:
I changed dispatch_get_main_queue() to a static variable:
let mainqueue = dispatch_get_main_queue()
and used mainqueue inplace of dispatch_get_main_queue but this did not do anything.
UPDATE 2:
I added a variable called tempProxy that is created each time delete is called:
let tempProxy = (self.textDocumentProxy as UIKeyInput)
Then in dispatch_async I used this:
tempProxy.deleteBackward()
I did this because I heard that having references to self could cause memory retention.
UPDATE 3:
I changed:
(self.textDocumentProxy as UIKeyInput).deleteBackward()
to a print() statement.
The memory usage was now a lot less. However, I was confused as to why the memory was still not being released.
Turns out I had NSZombie Objects enabled which left a permanent memory increase each time I called deleteBackward()
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
Does any body know what I need to check if app freezes after some time? I mean, I can see the app in the iPhone screen but no view responds.
I did some google and i found that, i've blocked the main thread somehow.
But my question is how to identify which method causes blocking of main thread? is there any way to identify?
Launch your app and wait for it to freeze. Then press the "pause" button in Xcode. The left pane should show you what method is currently running.
Generally, it is highly recommended to perform on the main thread all animations method and interface manipulation, and to put in background tasks like download data from your server, etc...
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//here everything you want to perform in background
dispatch_async(dispatch_get_main_queue(), ^{
//call back to main queue to update user interface
});
});
Source : http://www.raywenderlich.com/31166/25-ios-app-performance-tips-tricks
Set a break point from where the freeze occurs and find which line cause that.
Chances may be,Loading of large data,disable the controls,overload in main thread,Just find out where that occurs using breakpoints and rectify based on that.
I believe it should be possible to periodically check to see if the main thread is blocked or frozen. You could create an object to do this like so:
final class FreezeObserver {
private let frequencySeconds: Double = 10
private let acceptableFreezeLength: Double = 0.5
func start() {
DispatchQueue.global(qos: .background).async {
let timer = Timer(timeInterval: self.frequencySeconds, repeats: true) { _ in
var isFrozen = true
DispatchQueue.main.async {
isFrozen = false
}
DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + self.acceptableFreezeLength) {
guard isFrozen else { return }
print("your app is frozen, so crash or whatever")
}
}
let runLoop = RunLoop.current
runLoop.add(timer, forMode: .default)
runLoop.run()
}
}
}
Update October 2021:
Sentry now offers freeze observation, if you don't wanna roll this yourself.
I reached an error similar to this, but it was for different reasons. I had a button that performed a segue to another ViewController that contained a TableView, but it looked like the application froze whenever the segue was performed.
My issue was that I was infinitely calling reloadData() due to a couple of didSet observers in one of my variables. Once I relocated this call elsewhere, the issue was fixed.
Most Of the Time this happened to me when a design change is being called for INFINITE time. Which function can do that? well it is this one:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
}
Solution is to add condition where the function inside of viewDidLayoutSubviews get calls only 1 time.
It could be that another view is not properly dismissed and it's blocking user interaction! Check the UI Debugger, and look at the top layer, to see if there is any strange thing there.