Swift には、クラス型のプロパティを扱うときに、強い参照の循環を解決する方法が 2 つあります。弱い参照と非所有の参照です。

弱い参照と非所有の参照は、あるインスタンスが、別のインスタンスの参照を強く保持することなく、そのインスタンスを参照できるようにする参照循環です。従って、強い参照の循環を生成することなく、インスタンスが互いに参照することができます。

生存期間のどこかで参照が nil になることが妥当な場合には、弱い参照を利用します。反対に、初期化の間に設定されると、その後に参照が nil になることがないとわかっている場合には、非所有の参照を利用します。

弱い参照

弱い参照は、参照するインスタンスを強く保持し続けない参照で、参照されていたインスタンスを ARC が処理することを妨げません。この振る舞いは、参照が強い参照の循環になることを防ぎます。弱い参照とするには、プロパティや変数の宣言の前に weak キーワードを置きます。

生存中のどこかで参照が値を失う可能性がある場合、参照の循環を避けるために弱い参照を利用します。参照に常に値がある場合には、Unowned Reference で説明されているように、非所有の参照を利用します。前の Apartment の例では、アパートの生存期間のどこかでテナントがいないことはありえるので、このケースでは参照の循環を切れる弱い参照が適切な方法です。

NOTE
弱い参照は、実行時に値が変更できるように、変数として宣言する必要があります。弱い参照を定数として宣言することはできません。

弱い参照は参照するインスタンスを強く保持し続けないため、弱い参照がまだ参照している間に、そのインスタンスが割り当て解除されることがあります。従って、参照するインスタンスが割り当て解除されたときに、ARC は自動的に弱い参照に nil を設定します。弱い参照の値を nil にできるようにするため、弱い参照は常にオプショナル型とします。他のオプショナル型の値と同じようにして、弱い参照の値が存在しているかを確認することができ、存在しなくなった無効なインスタンスを参照したままになることはありません。

次の例は、1 点の重要な違いを除いて、前の Person と Apartment の例とまったく同じです。今回は、Apartment 型の tenant プロパティは弱い参照として宣言されています。

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?
    deinit { print("Apartment \(unit) is being deinitialized") }
}

前回どおり、2 つの変数(john と unit4A)と 2 つのインスタンス間のつながりから強い参照が生成されます。

var john: Person?
var unit4A: Apartment?

john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")

john!.apartment = unit4A
unit4A!.tenant = john

次は、2 つのインスタンスをつなげた参照がどうなっているかを示しています。

image: weakReference01_2x

Person インスタンスは依然として Apartment インスタンスに対する強い参照を持ちますが、Apartment インスタンスは Person インスタンスに対して弱い参照を持ちます。つまり、john 変数に nil を設定して強い参照を切ると、Person インスタンスに対する強い参照は無くなります。

john = nil
// "John Appleseed is being deinitialized" と出力

Person インスタンスに対する強い参照が無くなったため、割り当て解除されて tenant プロパティに nil が設定されます。

image: weakReference02_2x

残っている Apartment インスタンスに対する強い参照は、変数 unit4A からの参照です。その強い参照を切ると、Apartment インスタンスに対する強い参照は無くなります。

unit4A = nil
// "Apartment 4A is being deinitialized" と出力

Apartment インスタンスに対する強い参照が無くなったため、割り当て解除されます。

image: weakReference03_2x

NOTE
ガベージコレクションを利用するシステムでは、メモリの切迫によってガベージコレクションが起きるときにのみ、強い参照でないオブジェクトが割り当て解除されるため、シンプルなキャッシュの仕組みを実装するために 弱いポインタが利用される場合があります。一方で、ARC では、最後の強い参照が削除されるとすぐに値が割り当て解除されます。弱い参照はこの目的には適しません。

非所有の参照

弱い参照と同様、非所有の参照は、参照するインスタンスを強く保持し続けることはありません。一方で、弱い参照と異なり、非所有の参照には常に値があるものと想定されます。これにより、非所有の参照は常にオプショナルでない型で定義されます。非所有の参照とするには、プロパティや変数の宣言の前に unowned キーワードを置きます。

非所有の参照はオプショナルでないため、非所有の参照を使用するたびにアンラップする必要はありません。非所有の参照は、常に直接参照することができます。しかし、オプショナルでない型の変数に nil を設定することはできないため、参照するインスタンスが割り当て解除されたときに、ARC は参照に nil を設定することができません。

NOTE

参照するインスタンスが割り当て解除された後に、非所有の参照にアクセスしようとした場合、実行時エラーになります。参照が常にインスタンスを参照するときにのみ、非所有の参照を利用します。

参照するインスタンスが割り当て解除された後に、非所有の参照にアクセスしようとした場合、Swift は確実にアプリをクラッシュさせることにも注目してください。この状況において、予期しない振る舞いになることはありません。そうなることを防ぐべきですが、アプリは常に期待どおりクラッシュします。

以降の例では、銀行の顧客とその顧客のクレジットカードをモデル化する、2 つのクラス Customer と CreditCard を定義しています。各クラスはプロパティとして他方のクラスのインスタンスを保持します。この関係は、強い参照の循環を生成する可能性があります。

Customer と CreditCard 間の関係は、弱い参照の例で見た Apartment と Person 間の関係とは少し異なります。このデータモデルでは、顧客はクレジットカードを持っているかもしれませんし、持っていないかもしれませんが、クレジットカードは常に顧客に結びついています。これを表現するために、Customer クラスは card プロパティをオプショナルとしていますが、CreditCard クラスはオプショナルでない customer プロパティとしています。

さらに、新しい CreditCard インスタンスは、CreditCard イニシャライザに number 値と customer インスタンスを渡すことでしか生成できません。これにより、CreditCard インスタンスが生成されるときに、CreditCard インスタンスに結びつけられる customer インスタンスが常にあるようにしています。

クレジットカードには常に顧客があるため、customer プロパティを強い参照の循環を避ける非所有の参照として定義します。

class Customer {
    let name: String
    var card: CreditCard?
    init(name: String) {
        self.name = name
    }
    deinit { print("\(name) is being deinitialized") }
}

class CreditCard {
    let number: UInt64
    unowned let customer: Customer
    init(number: UInt64, customer: Customer) {
        self.number = number
        self.customer = customer
    }
    deinit { print("Card #\(number) is being deinitialized") }
}
NOTE
CreditCard クラスの number プロパティは、Int ではなく、32 ビットおよび 64 ビットの両システムで 16 桁のカード番号を保管するのに十分な UInt64 の型で定義されています。

次のコードスニペットは、特定の顧客に対する参照を保管するために使用する、Customer のオプショナル変数 john を定義しています。

var john: Customer?

次に、Customer インスタンスを生成し、新しい CreditCard インスタンスを初期化するためと、その顧客の card プロパティに代入するために利用します。

john = Customer(name: "John Appleseed")
john!.card = CreditCard(number: 1234_5678_9012_3456, customer: john!)

次は、2 つのインスタンスをつなげた結果、参照がどのようになっているかを示しています。

image: unownedReference01_2x

Customer インスタンスは CreditCard インスタンスに対する強い参照を持ち、CreditCard インスタンスは Customer インスタンスに対する非所有の参照を持っています。

customer の参照は非所有なため、変数 john による強い参照を切ると、Customer インスタンスに対する強い参照は無くなります。

image: unownedReference02_2x

Customer インスタンスに対する強い参照が無くなったため、割り当て解除されます。この結果、CreditCard インスタンスに対する強い参照も無くなるため、割り当て解除されます。

john = nil
// "John Appleseed is being deinitialized" と出力
// "Card #1234567890123456 is being deinitialized" と出力

最後のコードスニペットは、変数 john に nil が設定された後、Customer インスタンスと CreditCard インスタンスそれぞれのデイニシャライザが、デイニシャライズされたメッセージを出力していることを示しています。

非所有の参照と無条件にアンラップされるオプショナルプロパティ

上で見た弱い参照と非所有の参照の例は、強い参照の循環を切る必要がある一般的なシナリオをカバーしています。

Person と Apartment の例は、2 つのプロパティが共に nil になることがあり、強い参照の循環になる可能性がある状況を示していました。このシナリオの場合には、弱い参照で解決することがベストです。

Customer と CreditCard の例は、一方のプロパティは nil になることがあり、他方は nil になることがない、強い参照の循環になる可能性がある状況を示していました。このシナリオの場合には、非所有の参照で解決することがベストです。

しかしながら、両方のプロパティに常に値があり、初期化完了後にはプロパティが nil になることが無いという 3 つ目のシナリオがあります。このシナリオの場合には、一方のクラスを非所有の参照とし、他方のクラスを無条件にアンラップされるオプショナルプロパティとして組み合わせることが効果的です。

こうすることで、参照の循環を回避しつつ、初期化の完了後は直接(オプショナルをアンラップすることなく)それぞれのプロパティにアクセスできるようになります。そのような関係をセットアップする方法について見ていきます。

次の例では、2 つのクラス Country と City を定義していて、プロパティに他方のクラスのインスタンスを保持しています。このデータモデルでは、すべての国が常に首都を持つ必要があり、すべての都市が常に国に属する必要があります。これを表現するために、Country クラスには capitalCity プロパティがあり、City クラスには country プロパティがあります。

class Country {
    let name: String
    var 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
    }
}

2 つのクラス間の相互依存をセットアップするために、City のイニシャライザは Country インスタンスを取り、country プロパティでこのインスタンスを保持します。

City のイニシャライザは Country のイニシャライザ内から呼び出されます。けれども、Two-Phase Initialization で説明されているように、新しい Country インスタンスが完全に初期化されるまで、Country のイニシャライザは City のイニシャライザに self を渡すことはできません。

この要求に対処するために、Country クラスの capitalCity プロパティを、タイプアノテーションの最後にエクスクラメーションマークを付ける (City!) ことで、無条件にアンラップされるオプショナルプロパティとして宣言します。つまり、capitalCity プロパティは、他のオプショナルと同じように nil がデフォルト値ですが、Implicitly Unwrapped Optionals で説明されているように、値をアンラップする必要なくアクセスすることができます。

capitalCity のデフォルト値が nil であるため、Country インスタンスがイニシャライザ内で name プロパティを設定するとすぐに、新しい Country インスタンスは完全に初期化されているとみなされます。つまり、name プロパティが設定されるとすぐに、self プロパティを参照し、渡すことができるようになります。従って、Country イニシャライザが capitalCity プロパティを設定するとき、Country イニシャライザは City イニシャライザのパラメータの 1 つとして self を渡すことができます。

まとめると、強い参照の循環を生成することなく、Country と City のインスタンスを 1 文で生成することができ、オプショナル値をアンラップするためにエクスクラメーションマークを使う必要なく、capitalCity プロパティに直接アクセスすることができます。

var country = Country(name: "Canada", capitalName: "Ottawa")
print("\(country.name)'s capital city is called \(country.capitalCity.name)")
// "Canada's capital city is called Ottawa" と出力

この例では、無条件にアンラップされるオプショナルを使用することで、2 段階のクラスイニシャライザによるの必要条件のすべてが満たされています。強い参照の循環を回避しつつ、初期化の完了後はオプショナルでない値のように capitalCity プロパティにアクセスすることができます。


Portions of this page are translations based on work created and shared by Apple and used according to terms described in the Creative Commons Attribution 4.0 International License.