Reputation: 3750
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
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.
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.
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
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.
class MyClass {
init() {
println("ready")
}
deinit {
println("OK")
}
}
var aClass: MyClass?
aClass
aClass = MyClass()
aClass = nil
Upvotes: 0
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
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
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