I'm making another attempt today to try to understand retain cycles and weak references in Swift. Reading through the documentation , I saw the following code example where one of the referencing variables is marked as weak
to prevent a retain cycle:
class Person {
let name: String
init(name: String) { self.name = name }
var apartment: Apartment?
deinit { print("\(name) is being deinitialized") }
}
class Apartment {
let unit: String
init(unit: String) { self.unit = unit }
weak var tenant: Person? // <---- This var is marked as 'weak'
deinit { print("Apartment \(unit) is being deinitialized") }
}
var john: Person?
var unit4A: Apartment?
john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")
john!.apartment = unit4A
unit4A!.tenant = john
john = nil // prints "John Appleseed is being deinitialized"
unit4A = nil // prints "Apartment 4A is being deinitialized"
Is there any problem with making both variable weak? That is, in the Person
class, could I change the apartment
variable to be weak so that I have
class Person {
// ...
weak var apartment: Apartment? // added 'weak'
// ...
}
class Apartment {
// ...
weak var tenant: Person?
// ...
}
where there are two weak variables that reference each other.
I tested it out in the Playground and it seems to work ok, but is there any strong reason not to do this? It seems like in this case there is no natural parent-child relationship here.
You can do that. The only side effect is that you need to ensure that something else is retaining the people and the apartments. In the original code you just need to retain the people and the apartments (associated with people) will be retained for you.
Strictly speaking the people aren't killed when the apartments are demolished and the apartments aren't demolished when the people die so weak references in this scenario make sense. It's generally better to consider the relationship and ownership model you want and then decide how to achieve that.
To augment the accepted answer, here is a concrete example which demonstrates the behavior.
Try this is a Playground:
class Person {
let name: String
init(name: String) { self.name = name }
weak var apartment: Apartment?
deinit { print("\(name) is being deinitialized") }
}
class Apartment {
let unit: String
init(unit: String) { self.unit = unit }
weak var tenant: Person? // <---- This var is marked as 'weak'
deinit { print("Apartment \(unit) is being deinitialized") }
}
class Test {
var person: Person
init() {
person = Person(name: "Fred")
let unit2B = Apartment(unit: "2B")
person.apartment = unit2B
unit2B.tenant = person
print(person.apartment!.unit)
}
func test() {
print(person.apartment!.unit)
}
}
func go() {
let t = Test()
t.test() // crashes here!
}
go()
At the time of init
in class Test
, the apartment that has been created is retained by the local variable unit2B
. When init
is finished, the apartment will be deallocated because there are no longer any strong references holding it, so the program crashes when test
is called because person.apartment
is now nil
.
If you remove the weak
from weak var apartment
in class Person
, then this example won't crash because the apartment created in init
is retained by the person
who is retained by the class property person
.
The other way to fix the example is to make unit2B
be a property of class Test
. Then the apartment would have a strong reference holding it so unit2B
wouldn't be deallocated after init
.
If you remove weak
from both weak var apartment
in class Person
and from weak var tenant
in class Apartment
, then the example won't crash, but neither the Person
nor the Apartment
will be deallocated because of the retain cycle created by two objects holding strong references to each other.
Your question doesn't provide enough information for us to answer it. You need to step back and study iOS memory management.
The core concept is object ownership. When you create an object and store a pointer to it in a strong variable, the system increments the retain count for that object. When the variable goes out of scope or you store nil into it, the system decrements the retain count. When the retain count drops to zero, the object is deallocated.
In order for an object to continue to live, you need to have at least one strong reference to it. If you don't it will be deallocated.
A weak pointer is NOT an owning reference.
If the only references to an object are weak references, it will be deallocated, possibly immediately. Weak references are special; the compiler zeros them out when the object is deallocated. This means that you won't crash if you try to send a message to an object saved in a weak variable. If it was deallocated, the pointer is changed to nil and the message is simply ignored.
As pointed out by @vacawama, sending messages to a nil object is the Objective-C way of doing things. (I've been working full-time for a client lately in Objective-C, so that tends to be my mindset lately. The question was about Swift however.)
In Swift, you use optional chaining instead, and syntax like this:
object?.method().
With this Swift syntax, if object is nil, the method call is skipped.
If you have 2 objects that each have weak references to each other, that's fine, but somewhere else in your program you need to have strong (owning) references to both objects or they will be deallocated.
If you have 2 objects that have strong references to each other, you've created a "retain cycle", and unless you nil one of those pointers at some time in the future, the objects will never be deallocated. If you have 2 (or more) objects that have strong references to each other but you don't have any other references to those objects, you've caused a memory leak.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.