1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

自動参照カウントARC(Automatic Reference Counting)と弱参照(weak reference)と非所有参照(unowned reference)の比較

Last updated at Posted at 2024-03-24

ARCとは(簡単に説明)

自動参照カウント(Automatic Reference Counting、ARC)は、プログラム内で使用されるオブジェクトのメモリ管理を自動化する技術です。ARCは、オブジェクトがどのくらいの参照を持っているかを追跡し、その数がゼロになると自動的にメモリを解放します。

ARCの例

class Person {
    let name: String
    init(name: String) {
        self.name = name
        print("\(name) の初期化が進行中です")
    }
    deinit {
        print("\(name) のインスタンス割り当てが解除されました")
    }
}

var reference1: Person?
var reference2: Person?
var reference3: Person?

reference1 = Person(name: "John Appleseed")
// John Appleseed の初期化が進行中です
reference2 = reference1
reference3 = reference1
reference1 = nil
reference2 = nil
reference3 = nil
// John Appleseed のインスタンス割り当てが解除されました

この例では、Personクラスのインスタンスが作られ、reference1に割り当てられています。この時点で、このPersonインスタンスの参照カウントは1です。

reference2 = reference1
reference3 = reference1

reference2とreference3にも同じインスタンスを割り当てると、参照カウントは3になります。オブジェクトは3つの参照があるため、メモリ上に保持され続けます。

reference1 = nil
reference2 = nil

reference1とreference2をnilにすることで、これらの参照を解除します。参照カウントは1に減少しますが、まだreference3がインスタンスを参照しているため、オブジェクトは解放されません。

reference3 = nil
// 出力: Johnが解放されました

最後にreference3もnilにすると、参照カウントが0になり、インスタンスはメモリから解放されます。これがARCの基本的な動作です。

クラスインスタンス間の強循環参照

2 つのクラスインスタンスが互いに強参照を保持している状態があると、オブジェクトがメモリから解放されない問題が起こります

class Person {
    let name: String
    init(name: String) { self.name = name }
    var apartment: Apartment?
    deinit { print("\(name) のインスタンス割り当てが解除されました") }
}

class Apartment {
    let unit: String
    init(unit: String) { self.unit = unit }
    var tenant: Person?
    deinit { print("アパート \(unit) のインスタンス割り当てが解除されました") }
}

var john: Person?
var unit4A: Apartment?

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

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

john = nil
unit4A = nil
// アパート 4A のインスタンス割り当てが解除されましたが出力されない

Person インスタンスは Apartment インスタンスへの強参照を持ち、Apartment インスタンスは Person インスタンスへの強参照を持つようになりました。

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

john 変数と unit4A 変数で保持されている強参照を解除しても、Person インスタンスは Apartment インスタンスへの強参照を持ち、Apartment インスタンスは Person インスタンスへの強参照を持ったままなので参照カウントはゼロにはならず、インスタンスは ARC によって割り当て解除されません。

john = nil
unit4A = nil

強循環参照の解消

強循環参照を解決する方法として。弱参照(weak reference)と非所有参照(unowned reference)があります。

弱参照(weak reference)

弱参照は、オブジェクトへの参照を保持しますが、そのオブジェクトの参照カウントを増やしません。これにより、オブジェクトが他の場所で解放された場合、弱参照は自動的にnilになります。

class Person {
    let name: String
    init(name: String) { self.name = name }
    var apartment: Apartment?
    deinit { print("\(name) のインスタンス割り当てが解除されました") }
}

class Apartment {
    let unit: String
    init(unit: String) { self.unit = unit }
    weak var tenant: Person? //Apartmentクラスのインスタンスは、そのtenantであるPersonインスタンスへの弱参照を持ちます
    deinit { print("アパート \(unit) のインスタンス割り当てが解除されました") }
}

var john: Person?
var unit4A: Apartment?

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

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

john = nil
// John Appleseed のインスタンス割り当てが解除されました
unit4A = nil
// アパート 4A のインスタンス割り当てが解除されました

Person インスタンスは依然として Apartment インスタンスへの強参照を持っていますが、Apartment インスタンスは Person インスタンスへの弱参照を持っています。これは、john 変数を nil に設定してその強参照を解除すると、Person インスタンスへの強参照がなくなることを意味します。

john = nil
// John Appleseed のインスタンス割り当てが解除されました

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

Apartment インスタンスへの唯一の強参照は、unit4A 変数です。その強参照がなくなると、Apartment インスタンスへの強参照はなくなります。

unit4A = nil
// アパート 4A のインスタンス割り当てが解除されました

Apartment インスタンスへの強参照がなくなったため、この割り当ても解除されます

非所有参照(unowned reference)

弱参照と同様に、非所有参照は、インスタンスを強参照せず参照カウントを増やしません。一方のオブジェクトが他方のオブジェクトより長生きすることがないという保証がある場合に使用されます。

class Customer {
    let name: String
    var card: CreditCard?
    init(name: String) {
        self.name = name
    }
    deinit { print("\(name) のインスタンス割り当てが解除されました") }
}

class CreditCard {
    let number: UInt64
    unowned let customer: Customer
    init(number: UInt64, customer: Customer) {
        self.number = number
        self.customer = customer
    }
    deinit { print("カード番号 #\(number) のインスタンス割り当てが解除されました") }
}

var john: Customer?

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

john = nil
// John Appleseed のインスタンス割り当てが解除されました
// カード番号 #1234567890123456 のインスタンス割り当てが解除されました

Customer インスタンスには、CreditCard インスタンスへの強参照があり、CreditCard インスタンスには、Customer インスタンスへの非所有参照があります。
Customer は非所有参照なので、john 変数によって保持されている強参照を解除すると、Customer インスタンスへの強参照はなくなります。

Customer インスタンスへの強参照がなくなったため、割り当てが解除されます。これが発生すると、CreditCard インスタンスへの強参照もなくなり、割り当ても解除されます。

john = nil
// John Appleseed のインスタンス割り当てが解除されました
// カード番号 #1234567890123456 のインスタンス割り当てが解除されました

弱参照(weak reference)と非所有参照(unowned reference)の比較表

特徴 弱参照(Weak Reference) 非所有参照(Unowned Reference)
対象が解放された後 自動的にnilになる 自動的にnilにならず、アクセスすると実行時エラーの可能性
オプショナル型で使用される オプショナル型ではない
使用シナリオ 主にUIコンポーネントやデリゲートに使用される。デリゲートが必ずしも存在するとは限らない状況に対応。 相互に参照するが必ずしも同時に解放されない場合に使用。例えば、親子関係のオブジェクトで親が子を強く参照し、子が親を非所有参照で保持する場合。
使い分け 参照対象が自身よりも先にメモリが解放されるときに使う 参照対象が自身と同じかより後にメモリが解放されるときに使う

使い分けについて解説

Apartmentには必ずしもtenantがいるわけではありません。例えば、誰もそのアパートに住んでいない場合や、以前の居住者が引っ越して新しい居住者がまだ入居していない場合などです。このような状況では、tenant プロパティは nil になる可能性があるため、このプロパティをオプショナル型として扱う必要があります。弱参照(weak)はオプショナル型であり、参照しているオブジェクトがメモリから解放されると自動的に nil に設定されます。

一方CreditCardは、クレジットカードは所有者なしに存在することがないため、その所有者であるCustomerが存在する間のみ意味を持ちます。非所有参照(unowned)を使用することでCustomerの解放時にCreditCardも自動的に解放されることを保証します。

非所有参照と暗黙アンラップしたオプショナルプロパティ(Unowned References and Implicitly Unwrapped Optional Properties)

両方のプロパティに常に値が必要で、初期化が完了すると、どちらのプロパティもnilすることができない状態です。このシナリオでは、一方のクラスに unowned プロパティ、もう一方のクラスに暗黙アンラップオプショナルプロパティを使用します。例として、CountryクラスとCityクラスを示します。

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
    }
}

var country = Country(name: "カナダ", capitalName: "オタワ")
print("\(country.name) の首都は \(country.capitalCity.name) です")
// カナダ の首都は オタワ です

Countryは必ず首都であるCityが必要であり、Cityは必ず国に所属しているので両方のプロパティに常に値が必要でどちらのプロパティもnilすることができない状態です。
CountryのcapitalCityプロパティはCityのインスタンスを参照しますが、初期化時にはまだCityインスタンスが存在しないため、オプショナルの強制アンラップ型(!)で宣言されています。
unowned およびオプショナルの強制アンラップを使用することで、これらのオブジェクト間のライフサイクルと所有権を適切に管理して循環参照を防いでいます。

参考文献

The Swift Programming Language(日本語版)

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?