Swiftではクラスのインスタンスは、Objective-Cと同様、参照カウンタで管理されています。
インスタンスが初めて生成された時に参照カウントが1になり、以降そのインスタンスへの参照が増えると参照カウントがインクリメントされ、減るとデクリメントされます。そして、参照カウントが0になると、インスタンスも自動的に破棄されます。
ARCは、Automatic Reference Countingの略で、参照カウントの増減を自動的にやってくれる仕組みのことです。
ARCはObjective-Cでも利用でき、ARCの登場以前は、プログラマがインスタンスへの参照が増えるたびに、参照カウントを増やし、参照を減らすたびに、参照カウントを減らすということをプログラムで意識して管理していましたが、ARCによって参照カウントの管理が自動化され、処理の記述漏れによるメモリリークをほとんどのケースで気にせずコーディングできるようになりました。
参照カウンタで管理されるのはクラスのインスタンスのみです。構造体や列挙型に参照カウンタはありません。
強参照
クラスのインスタンスを生成すると、そのインスタンスの参照カウントは1になります。別の変数へコピーすると参照カウントが増えて2になります。変数にnilを代入して破棄すると、参照カウントが1減ります。参照カウントが0になるとインスタンスも破棄されます。
このように参照カウントを増減する参照をstrong reference(強参照)と呼びます。強参照により参照カウントが0より大きい間は、インスタンスが破棄されることはありません。
特に明記しない限りクラスのインスタンスは強参照となります。
具体例を以下にコードで示します。
/* ペットクラス */
class Pet {
let name: String
init(name: String) {
self.name = name
print("ペット(\(self.name))の生成")
}
deinit {
print("ペット(\(self.name))の破棄")
}
}
var pochi1, pochi2, pochi3: Pet?
pochi1 = Pet(name: "ポチ") // 参照カウント:1
/* 実行結果
ペット(ポチ)の生成
*/
pochi2 = pochi1 // 参照カウント:2
pochi3 = pochi1 // 参照カウント:3
pochi1 = nil // 参照カウント:2
pochi2 = nil // 参照カウント:1
pochi3 = nil // 参照カウント:0 ==> インスタンスの破棄
/* 実行結果
ペット(ポチ)の破棄
*/
Playgroundではインスタンス破棄のタイミングが不定なためか、上のコードをPlaygroundで実行してもデイニシャライザが呼ばれません。
循環参照
関連のある複数のクラスのインスタンス同士をお互いに強参照で保持すると、循環参照となりメモリリーク(インスタンスが破棄されずに残ること)する場合があります。
以下に例を示します。
/* 船クラス */
class Ship {
let name: String
var captain: Captain? // 船長
init(name: String) {
self.name = name
}
deinit {
print("船(\(self.name))の破棄")
}
}
/* 船長クラス */
class Captain {
let name: String // 船
var ship: Ship?
init(name: String) {
self.name = name
}
deinit {
print("船長(\(self.name))の破棄")
}
}
var merry: Ship? = Ship(name: "ゴーイングメリー号")
var rufy: Captain? = Captain(name: "ルフィ")
merry!.captain = rufy
rufy!.ship = merry
ここまでで、以下の図のような状態になっています。
ここで、merry変数にnilを設定すると、次のように船クラスのインスタンスの参照カウントが1減って1になります。まだ船長クラスから参照されているので0にはなりません。
merry = nil
次に、rufy変数にnilを設定します。すると次のように、船長クラスの参照カウントが1減って1になります。
rufy = nil
船クラスと船長クラスのインスタンスがお互いにそれぞれのインスタンスを保持し合っているため、参照カウントが0にならずメモリに残ったままになっています。
それぞれのインスタンスを指す変数はもうnilが代入されているので、これらのインスタンスにアクセスする手段は無く宙ぶらりんの状態で不要にメモリを占有しメモリリークしています。
このような循環参照を避けるには、少なくとも一方のクラスで強参照を使わず参照カウントに影響を与えないようにする必要があります。
Swiftではこのような場合に利用できる2種類の参照方法があります。1つが弱参照と呼ばれるもので、もう1つがアンオウンド参照(unowned:所有しないという意味)と呼ばれるものです。
参照するインスタンスが後からnilになるうる場合は弱参照を、一旦設定されたらその後nilになることがない(インスタンスの存続期間が一方に、或はお互いに依存する)場合はアンオウンド参照を使用します。
弱参照
参照先を設定した後その値がnilになり得る場合は弱参照を使います。上の例では、船長がやめたり別の船に乗り換えたりしてnilになるケースもあり得るので、弱参照を使うのが適しています。
弱参照を使うと指定先のインスタンスを保持せず、そのインスタンスが破棄された時は自動的にnilが代入されます。弱参照を使うには、プロパティの宣言時にweakをつけて宣言します。
また、値としてnilを許容する必要があるので型はオプショナル型である必要があります。上の例の場合はもともとオプショナル型にしていました。
/* 船クラス */
class Ship {
let name: String
weak var captain: Captain?
init(name: String) {
self.name = name
}
deinit {
print("船(\(self.name))の破棄")
}
}
/* 船長クラス */
class Captain {
let name: String
var ship: Ship?
init(name: String) {
self.name = name
}
deinit {
print("船長(\(self.name))の破棄")
}
}
var merry: Ship? = Ship(name: "ゴーイングメリー号")
var rufy: Captain? = Captain(name: "ルフィ")
merry!.captain = rufy
rufy!.ship = merry
必要に応じて船長クラスの船インスタンスを参照するプロパティもweakにしても良いのですが、一方をweakにするだけでも循環参照を回避することは可能です。
ここまでで、参照カウントは次のようになります。
船クラスの船長プロパティを弱参照にしたため、船長クラスのインスタンスの参照カウントは、rufy変数から参照されている1つのみとなっています。
ここで、mery変数にnilを代入すると次のようになります。
船クラスのインスタンスの参照カウントが1減って1になりました。この後、残ったrufy変数にnilを代入すると、船長クラスのインスタンスの参照カウントは0になり破棄されます。同時に、船長クラスのインスタンスが参照していた船クラスのインスタンスの参照カウントが1減って0になるため、船クラスのインスタンスも破棄されます。
逆に、最初の状態からmery変数にnilを代入する前に、rufy変数にnilを代入した場合、次のようになります。
船長クラスのインスタンスは最初の状態で参照カウントが1だったため、rufy変数にnilを代入した結果、船長クラスのインスタンスの参照カウントが0になり、船長クラスのインスタンスは破棄されます。
そして、船クラスのインスタンスへの参照カウントが1減って、1になります。また、船クラスのインスタンスが参照する船長クラスのインスタンスへのプロパティは弱参照となっているため、船長クラスのインスタンスの破棄に伴いnilが設定されます。
船クラスの船長プロパティには自動的にnilに設定されるので、次のようなコードで安全にプロパティにアクセスすることができます。
if let captain = merry?.captain {
print(captain.name)
}
この後は、mery変数にnilを代入することで、船クラスのインスタンスの参照カウントが0になり破棄されます。
これでメモリリークすることはなくなりました。
この結果も最初の例と同じようにPlaygroundでは検証できません。インスタンスの破棄によるデイニシャライザの出力結果を確認したい場合は、プロジェクトファイルを作ってシミュレータで動かす等して確認するか、REPL上で確認する必要があります。
アンオウンド参照
アンオウンド参照は、プロパティが一旦設定されたら、その後nilになることはないような場合に使用します。つまりそのプロパティのクラスに強く結びついていて、プロパティが先に破棄されることがないような場合です。
例を以下に示します。
/* パーソンクラス */
class Person {
let name: String // 氏名
var drivingLicence: DrivingLicense? // 運転免許証
init(name: String) {
self.name = name
}
deinit {
print("バーソン(\(self.name))の破棄")
}
}
/* 運転免許証クラス */
class DrivingLicense {
var number: Int64 // 免許証番号
unowned var holder: Person // 所有者
init(number: Int64, holder: Person) {
self.number = number
self.holder = holder
}
deinit {
print("運転免許証(\(self.number))の破棄")
}
}
ここでは、運転免許証とそれを保持するパーソンのクラスを使っています。バーソンは運転免許証を持たない可能性がありますが、運転免許証は必ず所有者がいるので、運転免許証クラスの所有者プロパティはnilになることはありません。そのため、unownedをつけてアンオウンド参照として宣言しています。また所有者プロパティはnilになることはないので、オプショナル型にはしていません。対してパーソンクラスの運転免許証プロパティはオプショナル型として宣言してあります。
アンオウンド参照も、弱参照と同様インスタンスの参照カウントに影響しません。またそのインスタンスが破棄された場合でもプロパティに自動的にnilが設定されることもありません。つまり、プロパティが先に破棄されることのないような状況で使用します。もし、プロバティが先に破棄されて、そのプロパティにアクセスすると、実行時エラーが発生します。
これらのインスタンスを生成してみます。
var taro: Person? = Person(name: "山野タロウ")
taro!.drivingLicence = DrivingLicense(number: 235_3421_0897_1025, holder: taro!)
運転免許証クラスの所有者プロパティをアンオウンドとしたので、パーソンクラスのインスタンスの参照カウントはtaro変数からの参照1つのみです。
この状態でtaro変数にnilを代入すると、パーソンクラスのインスタンスの参照カウントは0になり破棄されます。合わせてパーソンクラスのインスタンスから参照していた、運転免許証クラスのインスタンスも破棄されます。メモリーリークは起こりません。
もう1つの例を見てみます。上の例では、お互いを参照するクラスの一方はそのプロパティがnilになり得るが、もう一方はnilになり得ないという場合でした。
次に見る例は、お互いが強く結びついているため、参照する両方のプロパティがnilになり得ないというケースです。
ここでは、会社と社長という関係を使います。会社と社長はお互いに関連を持ち、それぞれ関連が無くなるということはあり得ません。つまりこれら2つのクラスのインスタンスを作成し、プロパティにお互いを設定した後はnilにすることはあり得ません。
/* 社長クラス */
class CompanyPresident {
let name: String // 氏名
unowned let conpany: Company // 会社
init(name: String, company: Company) {
self.name = name
self.conpany = company
}
deinit {
print("社長(\(self.name))の破棄")
}
}
/* 会社クラス */
class Company {
let name: String // 会社名
let president: CompanyPresident! // 社長
init(name: String, presidentName: String) {
self.name = name
self.president = CompanyPresident(name: presidentName, company: self)
}
deinit {
print("会社(\(self.name))の破棄")
}
}
var company: Company? = Company(name: "りんごカンバニー", presidentName: "スティーブ・ゲイツ")
図としてはその前の、パーソンと運転免許証クラスの図と同様です。循環参照を避けるために、社長クラスの会社プロパティをアンオウンド参照としています。会社プロパティはnilを許容しないので、ここではオプショナル型にはしていません。社長クラスのイニシャライザで社長名と会社インスタンスを受け取り、それをプロパティに設定しています。
会社クラスの方の実装は少し違っています。今回は会社クラスの社長プロパティもnilに出来ないので、イニシャライザで社長名を受け取り、社長クラスのインスタンスを生成してプロパティに設定しています。
ここで問題になるのは、社長クラスのイニシャライザに渡すためにselfが必要になるということです。イニシャライザの章で説明しましたが、selfにアクセスできるのは、インスタンスプロパティの設定が全て終わった後です。しかし、ここではそのプロパティの設定のためにselfが必要になるので、selfを使うに使えないというジレンマが発生します。
nilを許容するプロパティであればオプショナル型とすることで、初期値がnilで初期化済みになるので問題なくselfを使うことができますが、この場合はnilを許容しない方針なのでオプショナル型にするわけにもいきません。
そこで、ここでは社長プロパティを「暗黙的なオプショナル型」として宣言しています。「暗黙的なオプショナル型」はnilを許容する型なので、それ以外のプロパティの初期化が完了していればselfを安全に使うことができます。また、「暗黙的なオプショナル型」は実際にアクセスする時までには値が設定されている(nilではない)想定で使用する型なので、nilを許容しないという条件も満たすことができます。「暗黙的なオプショナル型」を使用することで、アンラップすることなく、社長プロパティにアクセスすることができます。
print(company!.president.name) // スティーブ・ゲイツ
/* 注意:company変数は上でオプショナル型として宣言したのでアンラップが必要です。presidentプロパティは「暗黙的なオプショナル型」なのでアンラップは不用です。*/
company = nil // インスタンスを破棄
クロージャの循環参照
クラスインスタンス同士の参照以外にも循環参照を引き起こすケースがあります。それは、クラスのプロパティにクロージャを割り当て、その中でselfを参照するような場合です。クロージャもクラスインスタンスと同様参照型なので現象としては同じです。
/* パスワード生成器クラス */
class PasswordGenerator {
// パスワードの長さ
var length: Int
// 使用可能文字
var allowedCharacters: [Character] {
var charas = [Character]()
// ASCII文字 "0"〜"}"
for ch in 0x30...0x7d {
charas.append(Character(UnicodeScalar(ch)))
}
return charas
}
// パスワードの生成
lazy var newPassword: () -> String = {
var pass = ""
for idx in 1...self.length {
let ch = self.allowedCharacters[Int(arc4random()) % self.allowedCharacters.count]
pass.append(ch)
}
return pass
}
// イニシャライザ
init(length: Int) {
self.length = length
}
}
var passwordGenerator: PasswordGenerator? = PasswordGenerator(length: 8)
print(passwordGenerator!.newPassword())
passwordGenerator = nil // !!!メモリリーク
上の例はパスワードを生成するためのクラスで、パスワード生成のロジックを変更できるようにパスワードを取得するためのプロパティをクロージャにしています。
このクロージャでは、selfを使ってインスタンスのプロパティにアクセスしています。クロージャは使用する外部のインスタンスをデフォルトで強参照で保持します。そのため下図の様に「インスタンス→クロージャ」、「インスタンス←クロージャ」という循環参照が発生しています。
このため、passwordGenerator変数にnilを代入しても、パスワード生成器のインスタンスもクロージャのインスタンスもメモリから破棄されません。
Swiftではこの様な状況を解決するために、キャプチャリストというものが提供されています。
キャプチャリストを使ってクロージャ内で使用するインスタンスの参照方法を指定することができます。キャプチャリストはweakやunownedといった参照方法とそのインスタンスのペアをカンマ区切りで指定するものです。
例えば、Int型とString型の引数をとりString型を返すクロージャ内で参照するselfをアンオウンド参照としたい場合は次のように記述します。
lazy var someProperty: (Int, String) -> String = {
[unowned self](number: Int, text: String) -> String in
:
}
また、引数を取らずString型を返すクロージャ内で参照するselfを弱参照としたい場合は次のように記述します。
lazy var someProperty: () -> String = {
[weak self] in // 戻り値の型は宣言から類推される
:
}
弱参照にするかアンオウンド参照にするかは、クラスのインスタンスプロパティ同士の場合と同じように、2つの依存度が高くインスタンスの破棄に合わせてクロージャも破棄する場合はアンオウンド参照を、後からプロパティにnilを設定したり、クラスインスタンスの破棄後もクロージャが存続する可能性がある場合は弱参照を使うようにします。
上のパスワード生成器クラスのクロージャをアンオウンド参照にして循環参照を回避するようにしてみます。
/* パスワード生成器クラス */
class PasswordGenerator {
// パスワードの長さ
var length: Int
// 使用可能文字
var allowedCharacters: [Character] {
var charas = [Character]()
// ASCII文字 "0"〜"}"
for ch in 0x30...0x7d {
charas.append(Character(UnicodeScalar(ch)))
}
return charas
}
// パスワードの生成
lazy var newPassword: () -> String = {
[unowned self] in
var pass = ""
for idx in 1...self.length {
let ch = self.allowedCharacters[Int(arc4random()) % self.allowedCharacters.count]
pass.append(ch)
}
return pass
}
// イニシャライザ
init(length: Int) {
self.length = length
}
}
newPasswordプロパティのクロージャの最初に[unowned self] inを追記しただけです。
これにより、クロージャからselfへの参照が強参カウントへの影響を与えないアンオウンド参照となり、循環参照を回避することができます。