Alexandros
Alexandros

Reputation: 553

Unwind segue (D->B) does not release C

This is a Swift 2.1 project with an iOS 8.0+ target.

Much like user Cybermew in this question, I have a set-up where A->B->C->D. A is a navigation controller and the rest are regular UIViewControllers. When unwinding from D to B, C does not get released. This results in a memory leak and other unintended side-effects.

As opposed to the question linked above, I don't have any NSTimer that I know of that might keep the view in memory. What could be causing this to happen?

Code of C (trimmed):

import UIKit
import SpriteKit
import AVFoundation
import AVFoundation.AVAudioSession

class GameViewController: UIViewController {

var scene: GameScene!
var level: Level!

@IBOutlet weak var gameOverPanel: UIImageView!
@IBOutlet weak var heroPicture: UIImageView!
@IBOutlet weak var villainPicture: UIImageView!
@IBOutlet weak var spellOneButton: UIButton!
@IBOutlet weak var doubleLife: UISwitch!

let attackSound = SKAction.playSoundFileNamed("Attack.wav", waitForCompletion: false)

var tapGestureRecognizer: UITapGestureRecognizer!

var backgroundMusic: AVAudioPlayer = {

    let sess = AVAudioSession.sharedInstance()
    if sess.otherAudioPlaying {
        _ = try? sess.setCategory(AVAudioSessionCategoryAmbient, withOptions: [])
        _ = try? sess.setActive(true, withOptions: [])
    }

    let url = NSBundle.mainBundle().URLForResource("BackgroundMusic", withExtension: "mp3")
    let player = try? AVAudioPlayer(contentsOfURL: url!)
    player!.numberOfLoops = -1

    return player!
}()

override func prefersStatusBarHidden() -> Bool {
    return true
}

override func shouldAutorotate() -> Bool {
    return true
}

override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
    return UIInterfaceOrientationMask.AllButUpsideDown
}

override func viewDidLoad() {
    super.viewDidLoad()

    let skView = view as! SKView
    skView.multipleTouchEnabled = false

    scene = GameScene(size: skView.bounds.size)
    scene.scaleMode = .AspectFill

    level = Level(filename: "Level_0")
    scene.level = level
    scene.addTiles()
    scene.swipeHandler = handleSwipe

    skView.presentScene(scene)
    backgroundMusic.play()
    beginGame()
}
}

Moved here from chat:

swipeHandler is declared as:

var swipeHandler: ((Swap) -> ())?

and swap is:

struct Swap: CustomStringConvertible, Hashable { 

    // blabla
}

and once filled with a swap if calls this on the GameViewController:

func handleSwipe(swap: Swap) { 

    view.userInteractionEnabled = false 

    if level.isPossibleSwap(swap) { 
        level.performSwap(swap) 
        scene.animateSwap(swap, completion: handleMatches) 
    } else { 
        scene.animateInvalidSwap(swap) { 
        self.view.userInteractionEnabled = true 
    } 
} 

Upvotes: 0

Views: 103

Answers (1)

vacawama
vacawama

Reputation: 154721

You have a reference loop that is causing your viewController not to be deallocated. I would recommend turning swipeHandler into a weak delegate pointer and you can call handleSwipe though that pointer. That way, your GameScene will not retain any strong pointers to your GameViewController which will allow it to be freed when the unwind segue is performed.

protocol SwipeHandler: class {
    func handleSwipe(swap: Swap)
}

class GameViewController: UIViewController, SwipeHandler {

    var level = Level()
    var scene: GameScene!

    override func viewDidLoad() {
        super.viewDidLoad()

        scene = GameScene()
        scene.swipeHandler = self
    }

    func handleSwipe(swap: Swap) {
        // ...    
    }
}

class GameScene {
    weak var swipeHandler: SwipeHandler?

    // function to demonstrate call to handleSwipe
    func useSwipeHandler() {
        let swap = Swap()
        swipeHandler?.handleSwipe(swap)
    }
}

Upvotes: 1

Related Questions