Brad Dwyer
Brad Dwyer

Reputation: 6494

Strong reference cycles in Swift

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

Answers (2)

nschum
nschum

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.

Edit

  • A third option is, to have 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

Sulthan
Sulthan

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

Related Questions