#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