Reputation: 6494
I'm looking at the example from the “Unowned References and Implicitly Unwrapped Optional Properties” section of the book “The Swift Programming Language.”
Their example code is
class Country {
let name: String
let capitalCity: City!
init(name: String, capitalName: String) {
self.name = name
self.capitalCity = City(name: capitalName, country: self)
}
}
class City {
let name: String
unowned let country: Country
init(name: String, country: Country) {
self.name = name
self.country = country
}
}
This works if I want to deal exclusively with Countries and the only purpose of the City
type is to be a capital of a Country
. But what happens if I want to create a City?
This creates a runtime exception because no reference to the City
's Country
is retained since it is an unowned variable:
var chicago = City(name:"Chicago", country: Country(name: "USA", capitalName: "Washington DC"))
chicago.country.name // Playground execution failed: error: Execution was interrupted, reason: EXC_BAD_ACCESS (code=EXC_I386_GPFLT).
How would I allow something like this without creating a Strong Reference Cycle?
Upvotes: 1
Views: 1478
Reputation: 15422
There are two typical solutions:
If you want to primarily deal with cities, invert the relationship so that City
has a strong reference to Country
, and Country
points back to an unowned instance.
If you want to have cities and countries as primary objects that cross reference each other, put all cities and countries into collections (or other form of store that owns them), and make both references weak. That way they don't own each other, and you don't have a cycle.
The best way to avoid retain cycles is to consider who owns every object. Objects can own each other, but that should be a clear hierarchy (i.e. a tree). If you have connections that go sidewards and and upwards in the hierarchy, make them weak or unowned.
Solution one is the upwards case, solution two is sidewards case.
Country
own a collection of all its cities. I think that makes most sense in this simple case, but it means the Country
needs to create all cities in it's initialization, or have a method that adds cities.Here's an example for the second case. It's quite complex, probably too much so for this simple case, but it illustrates extracting a common owner. I would normally use it if there are a lot of cross references. (Think relational database. The records don't own each other.)
class Country { let name: String weak var capitalCity: City? init(name: String) { self.name = name } } class City { let name: String unowned let country: Country init(name: String, country: Country, isCapital: Bool) { self.name = name self.country = country if isCapital { country.capitalCity = self } } } class Planet { var countries: [Country] = [] var cities: [City] = [] } let earth = Planet() earth.countries = [ Country(name: "USA"), Country(name: "Canada"), ] earth.cities = [ City(name: "Washington DC", country: earth.countries[0], isCapital: true), City(name: "Chicago", country: earth.countries[0], isCapital: false), City(name: "Ottawa", country: earth.countries[1], isCapital: true), ]
Upvotes: 4
Reputation: 130102
In your example, nobody is owning the Country
instance. That means it gets deallocated (freed) immediately.
var country = Country(name: "USA", capitalName: "Washington DC")
var chicago = City(name:"Chicago", country: country)
chicago.country.name
will fix it because our coutry
variable will keep USA
from deallocating
If you use an unowned reference, you always have to keep a strong reference somewhere else.
Upvotes: 0