Week 6 - Memory management

Goals

Preparation

Init and deinit

Apple's documentation provides a great example explaining what strong and weak references are all about. In this lab you will implement this example and verify that Automatic Reference Counting (ARC) works as advertised.

Retain cycle

Create a new Xcode project, name it prog6.1. Implement the Apartment and Person classes as given below:

01:
02:
03:
04:
05:
06:
07:
08:
09:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
import Foundation

/**
Class representing an apartment  Inherits from
RetainTracker, so that retain count of Apartment objects
can be seen.
*/
class Apartment : CustomStringConvertible  {
    // Apartment number
    let number: Int
    // Apartment's tenant (optional)
    var tenant: Person?
    
    /**
    String description
    */
    var description: String {
        return("Apartment \(number)")
    }
    
    /**
    Designated initialiser
    
    - parameter number: Apartment number
    */
    init(number: Int) {
        self.number = number
    }
}
01:
02:
03:
04:
05:
06:
07:
08:
09:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
import Foundation

/**
Class representing a person.  Inherits from
RetainTracker, so that retain count of Person objects
can be seen.
*/
class Person : CustomStringConvertible {
    // Name of the person
    let name: String
    // Optional apartment (where the person lives)
    var apartment: Apartment?
    
    /**
    String description
    */
    var description: String {
        return("Person \(name)")
    }
    
    /**
    Designated initialiser
    
    :parameter name: The name of the person
    */
    init(name: String) {
        self.name = name
    }
}

The classes are very simple. The Apartment object stores apartment number as Int and can reference a potential tenant, which is a Person object. The Person object stores a person's name as a String and references a potential apartment, which is an Apartment reference. If you've read carefully through the part on strong reference cycles you'll notice that at the moment there is a potential for reference cycle. Don't fix it just yet.

Here is some very simple code that creates a reference cycle between two objects of Apartment and Person type. The code is in a do-while loop, so that the created objects go out of scope before the program finishes. Memory occupied by the object going out of scope should be (in theory) freed by the ARC. Copy this program into your main.swift.

01:
02:
03:
04:
05:
06:
07:
08:
09:
10:
11:
import Foundation


repeat {
  var number73 = Apartment(number: 73)
  var john = Person(name: "John Smith")
    
  john.apartment = number73
  number73.tenant = john
    
} while(false)

If you run this code, there will be no output. It's hard to tell what's happening—are the objects destroyed, or not?

Tracking an object's life span

To get some visibility, add some debug information to class initialisers and deinitialisers.

01:
02:
03:
04:
05:
06:
07:
08:
09:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
import Foundation

/**
Class representing an apartment  Inherits from
RetainTracker, so that retain count of Apartment objects
can be seen.
*/
class Apartment : CustomStringConvertible  {
    // Apartment number
    let number: Int
    // Apartment's tenant (optional)
    var tenant: Person?
    
    /**
    String description
    */
    var description: String {
        return("Apartment \(number)")
    }
    
    /**
    Designated initialiser
    
    - parameter number: Apartment number
    */
    init(number: Int) {
        self.number = number
        // Print initialisation message
        print("\(self) is being itialized")
    }
    
    deinit {
        // Print deinitialisation message
        print("\(self) is being deinitialized")
    }
}
01:
02:
03:
04:
05:
06:
07:
08:
09:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
import Foundation

/**
Class representing a person.  Inherits from
RetainTracker, so that retain count of Person objects
can be seen.
*/
class Person : CustomStringConvertible {
    // Name of the person
    let name: String
    // Optional apartment (where the person lives)
    var apartment: Apartment?
    
    /**
    String description
    */
    var description: String {
        return("Person \(name)")
    }
    
    /**
    Designated initialiser
    
    - parameter name: The name of the person
    */
    init(name: String) {
        self.name = name
        // Print initialisation message
        print("\(self) is being initialized")
    }
    
    deinit {
        // Print deinitialisation message
        print("\(self) is being deinitialized")
    }
}

Run the main.swift again. This time you should see the output from initialisers like so:

Apartment 73 is being initialised
Person John Smith is being initialised

However, there is no output from either deinit. The deinit method (if implemented) is the last method that is always invoked on an object just before it gets destroyed. If deinit output didn't show in the output, it means that the object wasn't destroyed. From the above output it's evident that the two objects in main.swift are not deinitilised when they go out of scope. This is not unexpected—after all, we know that there is a retain cycle.

The fix

Go ahead and fix the code so that there is no retain cycle. If you're not sure how to go about it, read carefully the section on resolving strong reference cycles (and review the relevant lecture notes).

Run main.swift again. If your fix works, you should see a message printed from the deinitialisers of both objects, signifying their destruction when their references go out of scope within main.swift.

Leak fixing

The previous exercise was fairly easy. In this one, you'll be fixing memory leaks in a body of code that is a bit more complex. Here's a playground gameEngine.playground.zip for you to download. Unzip the file and double click on the playground project—it should open in Xcode and display the project as in the screenshot below.

If you don't see the project navigator on the left, from the menu select View->Navigators->Show Project Navigator. If you don't see the assistant editor windows on the right, from the menu select View->Assistant Editor->Show Assistant Editor. The playground auto-compiles and runs its code after any changes are made. It should also compile and run after you open it. It may take a while first time around, because all the files under the Sources folder need to be compiled. Afterwards, if you want to force a re-run, either make a simple change to the main code, or from menu select Editor->Execute Playground.

This project implements a simple game scene using the classes in the files under the Source folder. The scene should display an asteroid that strikes the rocket, at which point the rocket gets destroyed. The scene runs for 5 seconds and then stops. You can find more information about the structure of the game emulator library in the provided class diagrams and game loop flowchart. There are also comments in the code.

The part of the code that creates the scene and the objects visible in the scene is contained in the do-while loop shown below. The loop is there, again, to create a scope block so that all the created game objects and the scene itself goes out of scope after the scene is run.

001:
002:
003:
004:
005:
006:
007:
008:
009:
010:
011:
012:
013:
014:
015:
016:
017:
018:
019:
020:
021:
022:
023:
024:
025:
repeat {
    // Create a new scene
    var scene: Scene = Scene(width: 600, height: 600)
    
    // Create a static background sprite
    // and add it to the scene - default position is (0,0)
    scene.addGameObject(Sprite(imageNamed: "sun.png", scale: 0.5))


    // Create player object and add it to the scene - default
    // position is (0,0)
    var rocket = Player(imageNamed: "rocket.png", scale: 0.5)
    rocket.collider = RectangleCollider(width: 120, height: 60)
    scene.addGameObject(rocket)

    // Create enemy object, palce it at position (-150,150)
    // and add it to the scene
    var asteroid = Enemy(imageNamed: "asteroid.png", scale: 0.2)
    asteroid.position = CGPoint(x: -150, y: 150)
    asteroid.collider = CircleCollider(radius: 20)
    scene.addGameObject(asteroid)

    // Run the game engine
    runScene(scene, forTimeInSeconds: 5, atFrameRate: 10)
} while(false)

This version of the game emulator has been fitted with debug messages in the init and denit method of game objects as well as the scene object itself. When the scene runs, you should see the "Console output" with the init messages similar to the output below. If you don't see this window, from the main menu select View -> Debug Area -> Activate Console.

GameObject(0x00007fa773d85580) init.
Scene init.
Sprite "sun.png":GameObject(0x00007fa773c1c6a0) init.
Player:Sprite "rocket.png":GameObject(0x00007fa773d5fc40) init.
Rectangle:Collider:GameObject(0x00007fa773d61c70) init.
Sprite "asteroid.png":GameObject(0x00007fa773e384b0) init.
Circle:Collider:GameObject(0x00007fa773e3c4d0) init.

There are two problems with this library. When the rocket hits the ship, the game loop logic will removes the rocket ship (and its children) from the scene, but there is no deinit message from the rocket when asteroid strikes it. This means the objects remain in memory. Second problem is the fact that the scene, and all objects in it, are not destroyed at the end, when they go out of scope. Again, if they got destroyed properly, there would be deinit messages. You need to fix these two problems. It's got to do something with the relationship of game and scene objects. Good luck.

Note: You will need to change a file/files in the library itself to fix the problem. For whatever reason, in some versions of Xcode, the changes are not always immediately incorporated into the next run of the playground. Hence, make sure to run the playground a few times after making changes to any files under Sources folder, if the results do not change in the way that you expect that they should. This should assure that the Source changes are actually in the code that's being run.

Hint: You don't need to write a lot of code or understand all of the details regarding how the game system works. It's all about the relationships between objects. Taking a look at the game object hierarchy in the scene is a good start.