LoginSignup
1
1

More than 1 year has passed since last update.

SwiftのARCによるメモリ管理について(備忘録)

Last updated at Posted at 2021-08-08

ARC(Automatic Reference Counting)について

・ARCとは、Swiftにおけるメモリ管理の方式である。

・ARCでは、クラスのインスタンスを生成するたびにインスタンスのメモリ領域を確保し、不要になったタイミングで自動的にメモリを開放する。使用中のインスタンスのメモリが解放されてしまうことを防ぐために、プロパティ、変数、定数からそれぞれのクラスのインスタンスへの参照がいくつあるかをカウント(参照カウント)している。
・参照が増える -> 参照カウント +1
・参照が減る -> 参照カウント −1

そして、参照カウントが0になったタイミングでメモリが解放される。

・ARCによってインスタンスが破棄されるタイミングでは、クラスのデイニシャライザ(クリーンアップなどの終了処理)が実行される。

final class Dog {
    let name: String

    init(name: String) {
        self.name = name
    }

    // 参照カウントが0になりメモリが解放されたら実行される
    deinit{
        print("メモリが解放されました")
    }
}

var dogA: Dog? = Dog(name: "Hachi") // 参照カウント: 1  (初めてインスタンス化を行った際に参照カウントが1になる)
var dogB = dogA // 参照カウント: 2
var dogC = dogA // 参照カウント: 3

dogA = nil // 参照カウント: 2
dogB = nil // 参照カウント: 1
dogC = nil // 参照カウント: 0 (参照カウントが0になったタイミングでメモリが解放され、デイニシャライザが実行される)

循環参照とメモリリークについて

・メモリ管理において、(*1)循環参照によってメモリリークという問題が起きることがある。そのため、参照の種類を使い分けることで(*2)メモリリークを防ぐことができる。

・(*1)循環参照とは?

循環参照とは、2つのインスタンスが互いに強参照を持ち合う状態のことであり、参照カウントが0になることがなくなってしまう。そして、この循環参照がメモリリークという問題を引き起こす。

final class Dog {
    let name: String
    var owner: Owner?

    init(name: String) {
        self.name = name
    }

    deinit{
        print("メモリが解放されました: Dog")
    }
}

final class Owner {
    let name: String
    var dog: Dog?

    init(name: String) {
        self.name = name
    }

    deinit {
        print("メモリが解放されました: Owner")
    }
}
var dog: Dog? = Dog(name: "Hachi") // dogの参照カウント: 1
var owner: Owner? = Owner(name: "Ueno") // ownerの参照カウント: 1

// dogとownerがそれぞれ強参照している状態
dog?.owner = owner // ownerの参照カウント: 2 
owner?.dog = dog // dogの参照カウント: 2

dog = nil // dogの参照カウント: 1
owner = nil // ownerの参照カウント: 1

//メモリが解放されずに残ったままになってしまう

・(*2)メモリリークとは?

メモリリークとは、インスタンスが不要になっても参照カウントが0にならずに、残ってしまいメモリ領域を圧迫することでパフフォーマンスの低下を招いたり、場合によってはアプリケーションを終了させてしまう。

・参照の種類

①強参照: strong

デフォルトの参照の仕方であり、インスタンスが参照されると参照カウントが+1される。

②弱参照: weak

弱参照では、参照をしても参照カウントは変わらない。メモリが解放され、インスタンスが破棄されると、弱参照元には、nilが代入される

以下では、Ownerクラスのdogプロパティにweakキーワードをつけることで循環参照を防いでいる。

final class Dog {
    let name: String
    var owner: Owner?

    init(name: String) {
        self.name = name
    }

    deinit{
        print("メモリが解放されました: Dog")
    }
}

final class Owner {
    let name: String
    weak var dog: Dog?

    init(name: String) {
        self.name = name
    }

    deinit {
        print("メモリが解放されました: Owner")
    }
}
var dog: Dog? = Dog(name: "Hachi") // dogの参照カウント: 1
var owner: Owner? = Owner(name: "Ueno") // ownerの参照カウント: 1

dog?.owner = owner // ownerの参照カウント: 2
owner?.dog = dog // dogの参照カウント: 1(弱参照にしたことでdogの参照カウントは変わらない)

dog = nil // dogの参照カウント: 0 メモリが解放され,デイニシャライザが実行される。
// メモリが解放されました: Dog

//弱参照元にはnilが代入される。 ownerの参照カウント: 1
print(owner?.dog) // nil
owner = nil // ownerの参照カウント: 0 デイニシャライザが実行される
// メモリが解放されました: Owner

③非共有参照: unowned

非共有参照でも、弱参照と同じように参照をしても参照カウントは変わらない。しかし、弱参照との違いは、メモリが解放され、変数などにアクセスしようとするとエラーになってしまうこと。

final class Dog {
    let name: String
    var owner: Owner?

    init(name: String) {
        self.name = name
    }

    deinit{
        print("メモリが解放されました: Dog")
    }
}

final class Owner {
    let name: String
    unowned var dog: Dog?

    init(name: String) {
        self.name = name
    }

    deinit {
        print("メモリが解放されました: Owner")
    }
}
var dog: Dog? = Dog(name: "Hachi") // dogの参照カウント: 1
var owner: Owner? = Owner(name: "Ueno") // ownerの参照カウント: 1

dog?.owner = owner // ownerの参照カウント: 2
owner?.dog = dog // dogの参照カウント: 1

dog = nil // dogの参照カウント: 0
print(owner?.dog) //ここでエラーが発生する

参考

・Swift実践入門
・Swiftのメモリ管理を知る
https://shiba1014.medium.com/swiftのメモリ管理を知る-5fc4f4ade999

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