Need to free memory after Modal Segues, but I need

2019-09-10 23:54发布

I am making a game for iOS with SpriteKit.

I have 2 Viewcontrollers. One is the GameViewController and the other one is the MenuViewController. Let's call them A and B respectively.

When the player dies, a function is called in GameScene.swift that launches a modal "Lost" Segue to B. There, the player can restart the game or buy a life and a "Back" Segue is called to A.

I need to dismiss the additional Views that get created each time I call a segue.

Problem is: I need the "Lost" Segue to send data about the Score to View B and I need the "Back" Segue to send data to View A about wether or not the player used a life.

I have implemented all this. But now I need to find how to dismiss old views that keep eating the device's memory, thus leading to lag and crash.

I have googled for hours and hours. No solution was adapted to my situation.

The solutions I found either caused my app to bug, data not to be passed or views not to be generated.

I will not add code here since there is a LOT. But I am sure the answer is actually really easy, just not for a beginner like me.

I think a possible solution would be an unwind segue from B to A ?

But do unwind segues pass data along ?

Moreover, I found no answer I could understand on how to use an unwind segue.

I exhausted all my possibilities. Stack Exchange is my last chance.

1条回答
淡お忘
2楼-- · 2019-09-11 00:20

You definitely should use an unwind segue to return to the previous viewController, otherwise as you have found your memory usage increases until your apps quits.

I created the following example from your description. It uses a standard segue to move from the GameViewController to the MenuViewController and it uses an unwind segue to move from the MenuViewController back to the GameViewController.

The GameViewController has a Player Dies UIButton, a UITextField for entering a score, and a UILabel for displaying the lives.

The MenuViewController has a UILabel for showing the score, a Buy a Life UIButton for adding lives, and a Restart UIButton for returning to the GameViewController.

storyboard overview

Here's the code:

GameViewController.swift

import UIKit

class GameViewController: UIViewController {

    @IBOutlet weak var scoreTextField: UITextField!
    @IBOutlet weak var livesLabel: UILabel!

    var lives = 3

    func updateLivesLabel() {
        livesLabel.text = "Lives: \(lives)"
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        updateLivesLabel()
    }

    // This is the function that the unwind segue returns to.
    // You can call it anything you want, but it has to be in
    // the viewController you are returning to, it must be tagged
    // with @IBAction and it must take a UIStoryboardSegue as its
    // only parameter.
    @IBAction func returnFromMenu(segue: UIStoryboardSegue) {
        print("We're back in GameViewController")

        // Update the lives label based upon the value passed in
        // prepareForSegue from the MenuViewController.
        updateLivesLabel()
    }

    @IBAction func goPlayerDies(sender: UIButton) {
        lives--
        self.performSegueWithIdentifier("Lost", sender: self)
    }

    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        if segue.identifier == "Lost" {
            let destinationVC = segue.destinationViewController as! MenuViewController
            destinationVC.score = Int(scoreTextField.text ?? "") ?? 0
            destinationVC.lives = lives
        }
    }
}

MenuViewController.swift

import UIKit

class MenuViewController: UIViewController {

    @IBOutlet weak var scoreLabel: UILabel!

    var score = 0
    var lives = 0

    override func viewDidLoad() {
        super.viewDidLoad()

        scoreLabel.text = "Score: \(score)"
    }

    @IBAction func buyLife(sender: UIButton) {
        lives++
    }

    @IBAction func goRestart(sender: UIButton) {
        self.performSegueWithIdentifier("Back", sender: self)
    }

    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        if segue.identifier == "Back" {
            let destinationVC = segue.destinationViewController as! GameViewController
            destinationVC.lives = lives
        }
    }
}

This is how you wire up the forward segue to be called programmatically:

Control-drag from ViewController icon to the MenuViewController:

control drag to set up segue


Select Present Modally from the pop-up:

picture of pop-up


Click on the segue arrow between the viewControllers and give it an identifier in the Attributes Inspector:

identifier in attributes inspector


This is how you wire up the unwind segue to be called programmatically:

Control-drag from ViewController icon to Exit icon:

set up unwind segue


Choose returnFromMenu from pop-up:

choose return from Menu


Click on the Unwind Segue in the Document Outline and give it the identifier "Back" in the Attributes Inspector on the right:

enter image description here


Alternate Answer

Instead of using segues, you can present and dismiss viewControllers manually. The advantage for your app is that the MenuViewController will be allocated only once and will persist for the life of the app. This same viewController will be presented and dismissed repeatedly, but it will not be deallocated which I suspect is leading to your crashes.

The GameViewController will be the initialViewController that is created by the Storyboard. The MenuViewController will be loaded in viewDidLoad of the GameViewController.

To make this work, you need to add an identifier to the MenuViewController so that it can be instantiated by name. Click on the MenuViewController in the Storyboard and set its Storyboard ID in the Identity Inspector:

set storyboard ID


Here is the code. Note that all mention of segues is gone. Note how viewWillAppear is used to update the viewControllers.

GameViewController.swift

import UIKit

class GameViewController: UIViewController {

    @IBOutlet weak var scoreTextField: UITextField!
    @IBOutlet weak var livesLabel: UILabel!

    var menuViewController: MenuViewController?

    var lives = 3

    func updateLivesLabel() {
        livesLabel.text = "Lives: \(lives)"
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        menuViewController = self.storyboard!.instantiateViewControllerWithIdentifier("MenuViewController") as? MenuViewController
    }

    override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(animated)
        updateLivesLabel()
    }

    @IBAction func goPlayerDies(sender: UIButton) {
        lives--
        menuViewController?.score = Int(scoreTextField.text ?? "") ?? 0
        menuViewController?.lives = lives
        self.presentViewController(menuViewController!, animated: true, completion: nil)
    }

}

MenuViewController.swift

import UIKit

class MenuViewController: UIViewController {

    @IBOutlet weak var scoreLabel: UILabel!

    var score = 0
    var lives = 0

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override func viewWillAppear(animated: Bool) {
        super.viewWillAppear(animated)
        scoreLabel.text = "Score: \(score)"
    }

    @IBAction func buyLife(sender: UIButton) {
        lives++
    }

    @IBAction func goRestart(sender: UIButton) {
        let destinationVC = self.presentingViewController as! GameViewController
        destinationVC.lives = lives
        self.presentingViewController?.dismissViewControllerAnimated(true, completion: nil)
    }
}
查看更多
登录 后发表回答