Anton Tcholakov
Anton Tcholakov

Reputation: 3750

Weak references in Swift playground don't work as expected

I have been following the weak referencing example from the Intermediate Swift WWDC session in a Playground. I modified the code slightly as follows:

class Apartment {
    let address: Int

    init(address: Int) {
        self.address = address
    }

    weak var tenant: Person?
}

class Person {
    let name: String

    init(name: String){
        self.name = name
    }

    weak var home: Apartment?

    func moveIn(apt: Apartment) {
        self.home = apt
        apt.tenant = self
    }
}

var renters = ["John Appleseed": Person(name: "John Appleseed")]
var apts = [16: Apartment(address: 16)]

renters["John Appleseed"]!.moveIn(apts[16]!)
renters["John Appleseed"] = nil // memory should be released here

// then apts[16].tenant should be nil
if let tenantName = apts[16]!.tenant?.name {
    // this should only execute if the Person object is still in memory
    println("\(tenantName) lives at apartment number \(apts[16]!.address)")
} else {
    // and this line should execute if the memory is released as we expect
    println("Nobody lives at apartment number \(apts[16]!.address)")
}

// Console output in Playground: John Appleseed lives at apartment number 16
// Console output in standalone app: Nobody lives at apartment number 16

From my understanding of weak referencing, the memory allocated for the instance of Person should be released when it is removed from the renters dictionary because the only other reference to it is weak. However, the output of the programme is different if it is run as a standalone command line application vs. in a Playground (see comments).

Upvotes: 10

Views: 1217

Answers (5)

adazacom
adazacom

Reputation: 453

Update 3: As of 11.3.1: Playground seems to be deiniting objects as expected, as far as I can tell. My original and out of date answer follows: In Xcode 10.1 Playgrounds, I can confirm that deinits are still behaving strangely and I can't use Playgrounds to test whether things are being properly deallocated. Update 1: From another similar thread, I learned that Xcode>New>Project>macOS>Command Line Tool, is a relatively lightweight way to create a generic testing environment that works fine for testing deallocation.

class Person {
    let name: String
    init(named name: String) { self.name = name }
    var house: House?
    deinit { print("\(name) is being deinitialized") }
}

class House {
    let address: String
    init(address: String) { self.address = address }
    weak var tenant: Person?
    deinit { print("House \(address) is being deinitialized") }
}

func move(_ person: Person, into house: House){
    house.tenant = person
    person.house = house
}

When Person and House are unconnected, deinits work properly. enter image description here

However, if I move Buffy into the house, and delete Buffy, because tenant is weak, the Buffy object should be deinited and tenant set to nil. As you can see neither happens.

enter image description here

Even after I delete house (line 38), neither are deinited. Weak references are behaving like strong references in the Playground. Wrapping the Run code in a function does not change anything in this example. Update 2: In Xcode 11, As wbennet suggests above, if you wrap your run code in a func and call it, deallocations work for weak references as defined, in Playground.

Upvotes: 0

Owen Zhao
Owen Zhao

Reputation: 3355

It is not only weak reference. In playground, the deinit does not work. Since set a variable to nil can not let deinit run, it is not the time weak reference should work. deinit does not run

class MyClass {
    init() {
        println("ready")
    }
    deinit {
        println("OK")
    }
}
var aClass: MyClass?
aClass
aClass = MyClass()
aClass = nil

Upvotes: 0

wbennett
wbennett

Reputation: 2563

I believe the top-level function (REPL/playground) is keeping a strong reference to facilitate interactive behavior, and cleaning up when the frame returns. This behavior eliminates memory leaks in the interactive environment.

I copied Viktor's simple example and used the xcrun swift REPL.

In REPL mode, I wrapped the logic in a function and it works as expected. If/when you care when the memory is cleaned up, I would suggest wrapping your logic in a function.

// declaration of the types
class Person {
   let name: String
   weak var home: Apartment?

  init(pName: String){
      name = pName
  }

}

class Apartment {
    let postalCode: Int

    init(pPostalCode: Int) {
        postalCode = pPostalCode
    }
}

func testArc() {
    // create Person object
    var personJulius: Person = Person(pName: "Julius")

    // create Apartment object
    var apartmentBerlin: Apartment? = Apartment(pPostalCode: 10777)

    // connect Apartment object and Person object
    personJulius.home = apartmentBerlin

    // Set only strong reference of Apartment object to nil
    apartmentBerlin = nil

    // Person object should now have nil as home
    if personJulius.home != nil {
        println("Julius does live in a destroyed apartment")
    } else {
        println("everything as it should")
    }

}

//outputs "everything as it should"
testArc()

Upvotes: 6

Binarian
Binarian

Reputation: 12446

I tried an a bit less complex setup of the code. But did have the same problem in the Playground file, but not in a real command line project.

In a command line project, the output was everything as it should, and in the playground it was Julius does live in a destroyed apartment.

import Cocoa

// declaration of the types
class Person {
    let name: String
    weak var home: Apartment?

    init(pName: String){
        name = pName
    }

}

class Apartment {
    let postalCode: Int

    init(pPostalCode: Int) {
        postalCode = pPostalCode
    }
}

// create Person object
var personJulius: Person = Person(pName: "Julius")

// create Apartment object
var apartmentBerlin: Apartment? = Apartment(pPostalCode: 10777)

// connect Apartment object and Person object
personJulius.home = apartmentBerlin

// Set only strong reference of Apartment object to nil
apartmentBerlin = nil

// Person object should now have nil as home
if personJulius.home != nil {
    println("Julius does live in a destroyed apartment")
} else {
    println("everything as it should")
}

Upvotes: 2

Anton Tcholakov
Anton Tcholakov

Reputation: 3750

I guess that the Playground itself keeps a strong reference to the object, so the code behaves differently? If that's the case, this could cause some unexpected problems!

Upvotes: 2

Related Questions