Swift 5.8 日本語化計画 : Swift 5.8


自動参照カウント


オブジェクトの存続期間とそれらの関係をモデル化します。


Swift は、アプリのメモリ使用量を追跡し、管理するため 自動参照カウント (ARC) を使用します。ほとんどの場合、これはメモリ管理が Swift では "きちんと動く"ことを意味し、メモリ管理を自分自身で考える必要はありません。ARC は、クラスのインスタンスがもはや不要になったときにクラスインスタンスによって使用されるメモリを自動的に解放します。


しかし、いくつかの場合、ARC はメモリをあなたのために管理するために、コードの一部分の間の関係について、より多くの情報を必要とします。この章では、これらの状況を説明し、アプリのメモリをすべて管理するために、ARC を有効にする方法を示します。Swift での ARC の使用は、Objective-C で ARC を使用するための ARC への移行の公開ノート で説明したアプローチと非常によく似ています。


参照カウントは、クラスのインスタンスのみに適用されます。構造体と列挙型は、値型であり、参照型でないので、参照によって保存されたり渡されたりしません。



どのように ARC は働くか


クラスの新しいインスタンスを作成するたびに、ARC は、そのインスタンスに関する情報を格納するメモリの塊を割り当てます。このメモリは、そのインスタンスに関連した全ての格納されたプロパティの値と共に、インスタンスの型に関する情報を保持します。


加えて、インスタンスがもはや不要になったときには、ARC は、メモリが代わりに他の目的に使用することができるように、そのインスタンスによって使用されていたメモリを解放します。これは、それらが不要になったときに、クラスインスタンスがメモリ内のスペースを占有しないことを保証します。


しかし、ARC は、まだ使用されているインスタンスの割り当てを解除しようとする場合、もはやそのインスタンスのプロパティにアクセスしたり、そのインスタンスのメソッドを呼び出すことはできないでしょう。確かに、インスタンスにアクセスしようとした場合には、アプリはクラッシュする可能性が非常に高いでしょう。


インスタンスがまだ必要な間、それらが消えてしまわない事を確かめるために、多くのプロパティ、定数、および変数が現在、各クラスインスタンスを参照する方法を、ARC は追跡します。ARC は、そのインスタンスへの有効な参照が少なくとも一つまだ存在している間は、インスタンスの割り当てを解除しません。


これを可能にするため、プロパティ、定数、または変数にクラスインスタンスを代入するときはいつでも、そのプロパティ、定数、または変数は、インスタンスへの 強い参照 を行います。参照は、そのインスタンスをしっかりと保持しているため "強い" 参照と呼ばれ、その強い参照が残っている間は、割り当てを解除することはできません。



実際の ARC


ここで、どのように自動参照カウントが働くかの例を挙げます。この例では、name と言う格納された定数プロパティを定義する Person と言う簡単なクラスで始まります:


  1. class Person {
  2. let name: String
  3. init(name: String) {
  4. self.name = name
  5. print("\(name) is being initialized")
  6. }
  7. deinit {
  8. print("\(name) is being deinitialized")
  9. }
  10. }


Person クラスには、インスタンスの name プロパティを設定するイニシャライザがあり、その初期化が進行中であることを示すためにメッセージを出力します。Person クラスにはまた、クラスのインスタンスが割り当て解除されたときにメッセージを出力するデイニシャライザがあります。


次のコードスニペットは、後続のコードスニペットの新しい Person インスタンスへの複数の参照を設定するために使われる、Person? 型の3つの変数を定義しています。これらの変数は、optional 型 (Person ではなく Person?) なので、それらは自動的に nil の値で初期化され、現在は Person インスタンスを参照していません。


  1. var reference1: Person?
  2. var reference2: Person?
  3. var reference3: Person?


これで、新しい Person インスタンスを作成し、これら3つの変数の一つにそれを代入できます。


  1. reference1 = Person(name: "John Appleseed")
  2. // prints "John Appleseed is being initialized"


"John Appleseed is being initialized" というメッセージは、Person クラスのイニシャライザを呼び出した時点で出力されることに注意してください。これにより、初期化が行われたことが確認されます。


新しい Person インスタンスが reference1 変数に割り当てられているため、reference1 から新しい Person インスタンスへの強い参照ができます。少なくとも 1 つの強い参照があるため、ARC はこの Person がメモリ内に保持され、割り当て解除されないことを確認します。


さらに2つの変数に同じ Person インスタンスを割り当てた場合、そのインスタンスへのさらに2つの強い参照が確立されます。


  1. reference2 = reference1
  2. reference3 = reference1


現在、この一つの Person インスタンスに、3つの 強い参照が存在しています。


そのうち2つの変数に nil を代入して (元の参照を含む)、これらの強い参照の2つを壊す場合、一つの強い参照が残り、Person インスタンスの割り当ては解除されません。


  1. reference1 = nil
  2. reference2 = nil


3番目で最後の強い参照が壊れるまで、Person インスタンスをもはや使用しなくなったことが明らかな時点でも ARC は Person インスタンスの割り当てを解除しません。


  1. reference3 = nil
  2. // prints "John Appleseed is being deinitialized"


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


上記の例では、ARC は、作成した新しい Person インスタンスへの参照の数を追跡する事ができ、それがもはや不要になったときその Person インスタンスを割り当て解除できます。


しかし、クラスのインスタンスがゼロ個の強い参照を有する点には 決して 到達しないコードを書くことが可能です。2つのクラスインスタンスが、各インスタンスが他のインスタンスに生きたままでいようと、相互への強い参照を保持している場合これが発生することがあります。これは、強い循環参照 として知られています。


強い参照の代わりとして、弱い、または所有されていない参照としてクラス間の関係のいくつかを定義することにより、強い循環参照を解決できます。このプロセスは、クラスインスタンス間の強い循環参照の解決 で説明されています。しかし、強い循環参照を解決する方法を学ぶ前に、このような循環参照がなぜ起こったかを理解しておくと便利です。


ここに強い循環参照が偶然に作成されうる場合の例を示します。この例では、PersonApartment と言う2つのクラスを定義し、アパートのブロックとその住民をモデルにしています:


  1. class Person {
  2. let name: String
  3. init(name: String) { self.name = name }
  4. var apartment: Apartment?
  5. deinit { print("\(name) is being deinitialized") }
  6. }
  7. class Apartment {
  8. let unit: String
  9. init(unit: String) { self.unit = unit }
  10. var tenant: Person?
  11. deinit { print("Apartment \(unit) is being deinitialized") }
  12. }


すべての Person インスタンスには、String 型の name プロパティと、初期値は nil の、optional の apartment プロパティがあります。person はアパートを常には持っていないため、apartment プロパティは、optional です。


同様に、すべての Apartment インスタンスには、String 型の unit プロパティと、初期値は nil の、optional の tenant プロパティがあります。アパートはテナントを常には持っていないため、tenant プロパティは optional です。


これらのクラスの両方とも、そのクラスのインスタンスがデイニシャライズされているという事を印刷するデイニシャライザも定義しています。これで期待通りに PersonApartment のインスタンスが割り当て解除されているかどうかを確認できます。


次のコードスニペットは、下記の特定の ApartmentPerson インスタンスに設定されるだろう、johnunit4A と言う2つの optional 型の変数を定義しています。これらの変数はいずれも、optional であるおかげで、nil の初期値を持っています。


  1. var john: Person?
  2. var unit4A: Apartment?


これで、特定の Person インスタンスと Apartment インスタンスを作成し、johnunit4A 変数にこれらの新しいインスタンスを代入することができます。


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


強い参照がこれら2つのインスタンスを作成し、代入した後どのように見えるかここに示します。john 変数には今や、新しい Person インスタンスへの強い参照があり、unit4A 変数には、新しい Apartment インスタンスへの強い参照があります:





person には apartment があり、apartment には tenant があり、一緒に2つのインスタンスをリンクできるようになりました。感嘆符 (!) は、それらのインスタンスのプロパティが設定できるように、johnunit4A の optional 変数内に格納されたインスタンスを開封し、アクセスできるように使用されていることに注意してください:


  1. john!.apartment = unit4A
  2. unit4A!.tenant = john


ここで、2つのインスタンスを一緒にリンクした後、強い参照がどう見えるかを示します。




残念ながら、これら2つのインスタンスをリンクすると、それらの間の強い循環参照を作成してしまいます。Person インスタンスは現在、Apartment インスタンスへの強い参照を持っており、Apartment インスタンスは Person インスタンスへの強い参照を持っています。そのため、johnunit4A 変数で保持された強い参照を壊しても、参照カウントはゼロにならず、インスタンスは、ARC によって割り当て解除されません。


  1. john = nil
  2. unit4A = nil


これら2つの変数を nil に設定しても、どちらのデイニシャライザも呼び出されないことに注意してください。強い循環参照は、割り当てが解除されることからいつまでも PersonApartment インスタンスを防ぎ、アプリ内のメモリリークを引き起こします。


ここで、johnunit4A 変数を nil に設定した後、強い参照がどう見えるかについて示します。





Person インスタンスと Apartment インスタンスの間には強い参照が残っていて、壊すことができません。



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


Swift は、クラス型のプロパティを操作する際の強い循環参照を解決する為に2つの方法を提供しています:弱い参照と所有されていない参照です。


弱い参照と所有されていない参照は、その上に強い保持を維持すること なく、他のインスタンスを参照するための循環参照で1つのインスタンスを有効にします。インスタンスは、その後、強い循環参照を作ることなく、相互に参照することができます。


他のインスタンスの寿命が短い場合 (つまり、他のインスタンスを最初に割り当て解除できる場合) には、弱い参照を使用します。上に挙げた Apartment の例では、アパートの寿命中のある時点でテナントを持てないようにすることが適切であり、この場合、弱い参照が循環参照を壊す適切な方法です。これとは対照的に、他のインスタンスの寿命が同じか長い寿命である場合、所有されていない参照を使用します。



弱い参照


弱い参照 は、参照されるインスタンスの廃棄から ARC を中止しないので、それが参照するインスタンスに強い保持を維持しない参照です。この動作は、強い循環参照の一部になる事から参照を防止します。プロパティまたは変数宣言の前に weak キーワードを書いて、弱い参照である事を示して下さい。


弱い参照は、それが参照するインスタンスに強い保持を維持しないので、弱い参照がまだそれを参照している間に、そのインスタンスの割り当てを解除する事が可能です。そのため、割り当て解除されるインスタンスを参照している時、ARC は自動的に弱い参照を nil に設定します。そして、弱い参照は、それらの値が、実行時に nil に変更できるようにする必要があるため、それらは常に、定数ではなく、変数として optional の型を宣言されています。


他の optional の値と同様に、弱い参照内に値が存在するかどうかを確認できますし、無効なインスタンスへの参照が存在しなくなることは決してありません。



注意: ARC が弱い参照を nil に設定するときには、プロパティ監視者は呼び出されません。


以下の例は、上記の PersonApartment の例と1つの重要な違いを除いて全く同じです。今度は、Apartment 型の tenant プロパティは弱い参照として宣言されています。


  1. class Person {
  2. let name: String
  3. init(name: String) { self.name = name }
  4. var apartment: Apartment?
  5. deinit { print("\(name) is being deinitialized") }
  6. }
  7. class Apartment {
  8. let unit: String
  9. init(unit: String) { self.unit = unit }
  10. weak var tenant: Person?
  11. deinit { print("Apartment \(unit) is being deinitialized") }
  12. }


2つの変数 (johnunit4A) からの強い参照と2つのインスタンス間のリンクが以前のように作成されます。


  1. var john: Person?
  2. var unit4A: Apartment?
  3. john = Person(name: "John Appleseed")
  4. unit4A = Apartment(unit: "4A")
  5. john!.apartment = unit4A
  6. unit4A!.tenant = john


ここで、2つのインスタンスを一緒にリンクした時参照がどう見えるか示します:




Person インスタンスには、まだ Apartment インスタンスへの強い参照がありますが、Apartment インスタンスには今や、Person インスタンスへの 弱い 参照しかありません。これは、john 変数を nil に設定して、保持する強い参照を壊すと、Person インスタンスへの強い参照がもはや存在しないことを意味します。



  1. john = nil
  2. // prints "John Appleseed is being deinitialized"


Person インスタンスへの強い参照がもはやないので、それは割り当て解除され、tenant プロパティは nil に設定されます。




Apartment インスタンスへの唯一残った強い参照は unit4A 変数からです。その 強い参照を壊すと、 Apartment インスタンスへの強い参照はもはやありません。


  1. unit4A = nil
  2. // prints "Apartment 4A is being deinitialized"


Apartment インスタンスへの強い参照はもはやないので、それも割り当て解除されます:




注意: ガベージコレクションを使用しているシステムでは、メモリ圧迫がガベージコレクションを引き起こしたときにのみ、強い参照を持たないオブジェクトは、割り当てが解除されるため、弱いポインタは時々単純なキャッシュ・メカニズムを実装するために使用されます。しかし、ARC では、そのような目的のためには弱い参照は適さないため、それらの最後の強い参照が削除されるやいなや値は割り当て解除されます。


所有されていない参照


弱い参照と同様に、インスタンス上でそれが参照する強力な保持を、所有されていない参照 は維持しません。しかし、弱い参照とは異なり、所有されていない参照は、他のインスタンスが同じ寿命か長い寿命を持っている時に使われます。プロパティまたは変数の宣言の前に unowned キーワードを書くことによって所有されていない参照を示します。


弱い参照とは異なり、所有されていない参照は常に値を持つことが期待されます。その結果、値を所有されていないとしてマークしても optional にはならず、ARC は所有されていない参照の値を nil に設定することは決してありません。


重要: 参照が 常に 割り当て解除されていないインスタンスを参照していると確信している場合にのみ、所有されていない参照を使用してください。

そのインスタンスの割り当てが解除された後に、所有されていない参照の値にアクセスしようとすると、実行時エラーが発生します。


以下の例では、2つのクラス、CustomerCreditCard を定義しており、銀行の顧客とその顧客用の、ありえるクレジットカードをモデルにしています。これら2つのクラスはそれぞれ、プロパティとして、他のクラスのインスタンスを格納します。この関係は、強い循環参照を作成する可能性があります。


CustomerCreditCard の関係は、上記の弱い参照の例に見られる ApartmentPerson の関係とは少し異なります。このデータモデルでは、顧客は、クレジットカードを持っていてもいなくてもよいですが、クレジットカードは、常に 顧客に関連付けられます。CreditCard インスタンスは、それが参照する Customer よりも長生きすることは決してありません。これを表現するために、Customer クラスには、optional の card プロパティがありますが、CreditCard クラスには所有されていない (optional でない) customer プロパティがあります。


さらに、新しい CreditCard インスタンスは、カスタムの CreditCard イニシャライザに number 値と customer インスタンスを渡すことで のみ 作成することができます。これは、CreditCard インスタンスが作成されるとき、CreditCard インスタンスは常に、それに関連した customer インスタンスを持っていることを確実にします。


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


  1. class Customer {
  2. let name: String
  3. var card: CreditCard?
  4. init(name: String) {
  5. self.name = name
  6. }
  7. deinit { print("\(name) is being deinitialized") }
  8. }
  9. class CreditCard {
  10. let number: UInt64
  11. unowned let customer: Customer
  12. init(number: UInt64, customer: Customer) {
  13. self.number = number
  14. self.customer = customer
  15. }
  16. deinit { print("Card #\(number) is being deinitialized") }
  17. }


注意: CreditCard クラスの number プロパティは Int ではなく UInt64 型として定義されていますが、 number プロパティの容量が 32 ビットと 64 ビットの両方のシステムで 16 桁のカードの番号を格納するのに十分な大きさであることを保証するためです。


次のコードスニペットは、john と言う optional の Customer 変数を定義し、特定の customer (顧客) への参照を格納するために使用されます。この変数は optional であるおかげで、nil の初期値を持っています:


var john: Customer?



これで、Customer インスタンスを作成し、初期化し、その customer (顧客) の card プロパティとして新しい CreditCard インスタンスを割り当てて使用できるようになりました。


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


ここで、2つのインスタンスをリンクした今、参照がどのように見えるかを示します。





Customer インスタンスには今や、CreditCard インスタンスへの強い参照があり、CreditCard インスタンスには Customer インスタンスへの所有されていない参照があります。


所有されていない customer の参照のため、john 変数で保持される強い参照を壊すと、Customer インスタンスへの強い参照はもはやありません。





Customer インスタンスへの強い参照がもはやないので、それは割り当て解除されます。これが起こった後、 CreditCard インスタンスへの強い参照はもはやなくなり、それもまた割り当て解除されます:


  1. john = nil
  2. // prints "John Appleseed is being deinitialized"
  3. // prints "Card #1234567890123456 is being deinitialized"


上記に示した最後のコードスニペットは、Customer インスタンスと CreditCard インスタンスのためのデイニシャライザが、john 変数が nil に設定された後 "deinitialized" のメッセージを両方とも印刷する事を示しています。



注意: 上記の例では、安全な 所有されていない参照を使用する方法を示しています。Swift はまた、実行時の安全性チェックを無効にする必要がある場合 (たとえば、パフォーマンス上の理由など) に、安全でない 所有されていない参照も提供します。すべての安全でない操作と同様に、あなたにはそのコードの安全性をチェックする責任があります。

unowned(unsafe) を書いて、安全でない所有されていない参照を指定して下さい。参照するインスタンスが割り当て解除された後で、安全でない所有されていない参照にアクセスしようとすると、プログラムはそのインスタンスが使用していたメモリ位置にアクセスしようとしますが、それは安全でない操作です。


所有されていない Optional の参照


クラスへの optional の参照を所有されていない (unowned) としてマークできます。ARC 所有権モデルに関しては、所有されていない optional の参照と弱い参照の両方を同じ文脈で使用できます。違いは、所有されていない optional の参照を使用する場合、それが常に有効なオブジェクトを参照するか、nil に設定されていることをあなたは確認する必要があることです。


学校の特定の部門が提供するコースを追跡する例を以下に示します。


  1. class Department {
  2. var name: String
  3. var courses: [Course]
  4. init(name: String) {
  5. self.name = name
  6. self.courses = []
  7. }
  8. }
  9. class Course {
  10. var name: String
  11. unowned var department: Department
  12. unowned var nextCourse: Course?
  13. init(name: String, in department: Department) {
  14. self.name = name
  15. self.department = department
  16. self.nextCourse = nil
  17. }
  18. }


Department (部門) は、部門が提供する各コースへの強い参照を維持します。ARC 所有権モデルでは、部門はそのコースを所有します。Course には 2 つの所有されていない参照があります。1 つは department への参照で、もう 1 つは学生が受講するべき次のコースへの参照です。コースはこれらのオブジェクトのいずれも所有していません。すべてのコースは何らかの部門の一部であるため、department のプロパティは optional ではありません。ただし、一部のコースには推奨される後続コースがないため、nextCourse プロパティは optional です。


これらのクラスの使用例を以下に示します。


  1. let department = Department(name: "Horticulture")
  2. let intro = Course(name: "Survey of Plants", in: department)
  3. let intermediate = Course(name: "Growing Common Herbs", in: department)
  4. let advanced = Course(name: "Caring for Tropical Plants", in: department)
  5. intro.nextCourse = intermediate
  6. intermediate.nextCourse = advanced
  7. department.courses = [intro, intermediate, advanced]


上記のコードは、部門とその3つのコースを作成します。intro コースと intermediate コースの両方で、nextCourse プロパティに格納されている推奨される次のコースがあります。これは、学生がこのコースを完了した後に受講すべきコースへの所有されていない optional の参照を維持します。




所有されていない optional の参照は、包み込むクラスのインスタンスを強力に保持しないため、ARC によるインスタンスの割り当て解除を妨げることはありません。所有されていない optional の参照が nil になりうることを除いて、ARC での所有されていない参照と同じように動作します。


optional でない、所有されていない参照と同様に、nextCourse が常に割り当て解除されていないコースを参照するようにする責任があなたにはあります。この場合、たとえば、department.courses からコースを削除するときは、他のコースが持つかもしれないそのコースへの参照もすべて削除する必要があります。


注意: optional の値の基になる型は Optional です。これは、Swift 標準ライブラリの列挙型です。ただし、optional は、値の型を unowned としてマークできないという規則の例外です。

クラスを包み込む optional は参照カウントを使用しないため、optional への強い参照を維持する必要はありません。


所有されていない参照と暗黙に開封された Optional のプロパティ


上記に示した弱い参照と所有されていない参照の例は、強い循環参照を断ち切るのに必要な、より一般的な2つのシナリオをカバーしています。


PersonApartment の例では、両方とも nil であることが許されている2つのプロパティの場合を示し、強い循環参照を引き起こす可能性があります。このシナリオは、弱い参照で最も良く解決されます。


CustomerCreditCard の例では、nil であることが許されている1つのプロパティと、強い循環参照参照を引き起こす可能性のある、nil であることはできない別のプロパティの場合を示しています。このシナリオは、所有されていない参照で最も良く解決されます。


しかし、両方の プロパティが常に値を持つべき第三のシナリオがあり、どちらのプロパティとも、一度初期化が完了すると nil には決してなりえません。このシナリオでは、他のクラス上の暗黙的に開封された optional のプロパティと1つのクラスの所有されていないプロパティを組み合わせることが有効です。


これは、初期化が一度完了すると、まだ循環参照を回避しながら、両方のプロパティは、(optional の開封なしで) 直接アクセスすることができます。このセクションでは、そのような関係をどのように設定するかを示します。


以下の例では、プロパティとして、他のクラスのインスタンスをそれぞれ格納する2つのクラス、CountryCity を定義しています。このデータモデルでは、すべての country (国) は常に、首都を持っていなければならず、すべての city (都市) は常に country に属していなければなりません。これを表現するために、Country クラスには capitalCity プロパティがあり、City クラスには country プロパティがあります。


  1. class Country {
  2. let name: String
  3. var capitalCity: City!
  4. init(name: String, capitalName: String) {
  5. self.name = name
  6. self.capitalCity = City(name: capitalName, country: self)
  7. }
  8. }
  9. class City {
  10. let name: String
  11. unowned let country: Country
  12. init(name: String, country: Country) {
  13. self.name = name
  14. self.country = country
  15. }
  16. }


2つのクラス間の相互依存性を設定する為に、City のイニシャライザは、Country のインスタンスを取り、このインスタンスをその country プロパティに格納します。


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


この要件に対処するには、その型注釈の最後に感嘆符を付けて (City!)、暗黙的に開封された optional のプロパティとして CountrycapitalCity プロパティを宣言して下さい。これは capitalCity プロパティは、他の optional のように nil のデフォルト値を持っていますが、暗黙的に開封された Optional で説明したように、その値を開封する必要なくアクセスできることを意味します。


capitalCity はデフォルトで nil 値を持っているので、新しい Country インスタンスは、Country インスタンスが、そのイニシャライザ内の name プロパティを設定するやいなや完全に初期化されたとみなされます。これは Country イニシャライザが参照し始める事ができ、name プロパティが設定されるやいなや、暗黙的な self プロパティを渡せることを意味します。Country イニシャライザは、そのため Country イニシャライザが独自の capitalCity プロパティを設定している時 City イニシャライザのパラメータの一つとして self を渡す事ができます。


このすべては、強い循環参照を作成せずに、一つの文で CountryCity インスタンスを作成でき、また capitalCity プロパティがその optional 値を開封するために感嘆符を使用することなしに、直接アクセスできることを意味します。


  1. var country = Country(name: "Canada", capitalName: "Ottawa")
  2. print("\(country.name)'s capital city is called \(country.capitalCity.name)")
  3. // prints "Canada's capital city is called Ottawa"


上記の例では、暗黙的に開封された optional の使用は、二相のクラスイニシャライザの要件がすべて満たされていることを意味します。初期化が一度完了すると capitalCity プロパティは使用でき、optional でない値と同じようにアクセスでき、まだ強い循環参照を回避できます。



クロージャの強い循環参照


2つのクラスインスタンスプロパティがお互いへの強い参照を保持する際に、強い循環参照を作成する事を上で示しました。また、これらの強い循環参照を壊すのに、弱い参照と所有されていない参照を使う方法も説明しました。


クラスインスタンスのプロパティにクロージャを割り当てた場合にも、強い循環参照が発生することがあり、そしてそのクロージャの本体は、インスタンスをキャプチャします。このキャプチャはクロージャの本体が self.someProperty のようにインスタンスのプロパティにアクセスするために発生し、またはクロージャが self.someMethod() のようにインスタンス上のメソッドを呼び出すと発生します。いずれの場合でも、これらのアクセスは、強い循環参照を作成し、クロージャに self を "キャプチャ" する事を引き起こします。


クロージャは、クラスのように、参照型 であるため、この強い循環参照が発生します。プロパティにクロージャを割り当てると、そのクロージャに 参照 を割り当てています。本質的に、それは2つの強い参照が生きていてお互いを維持している、上記と同様の問題があります。しかし、むしろ2つのクラスインスタンスよりも、ここでは、生きてお互いを維持しているクラスインスタンスとクロージャです。


Swift は クロージャキャプチャリスト として知られている、この問題へのエレガントな解決法を提供します。しかし、クロージャキャプチャリストで強い循環参照を壊す方法を学ぶ前に、このような循環参照がどのように発生するかを理解しておくと便利です。


以下の例では、self を参照するクロージャを使用しているときに、強い循環参照を作成してしまう場合を示しています。この例では、HTMLElement と言うクラスを定義し、HTML の書類内の個々の要素の単純なモデルとして提供しています。


  1. class HTMLElement {
  2. let name: String
  3. let text: String?
  4. lazy var asHTML: () -> String = {
  5. if let text = self.text {
  6. return "<\(self.name)>\(text)</\(self.name)>"
  7. } else {
  8. return "<\(self.name) />"
  9. }
  10. }
  11. init(name: String, text: String? = nil) {
  12. self.name = name
  13. self.text = text
  14. }
  15. deinit {
  16. print("\(name) is being deinitialized")
  17. }
  18. }


HTMLElement クラスは、見出し要素の "h1"、段落要素の "p"、または改行要素の "br" のように、要素の名前を示す name プロパティを定義しています。HTMLElement はまた、その HTML 要素内にレンダリングされるテキストを表す文字列に設定できる optional の text プロパティも定義しています。


これら二つの単純なプロパティに加えて、HTMLElement クラスは asHTML と言う遅延した(lazy) プロパティを定義しています。このプロパティは、HTML 文字列の断片に nametext を組み合わせるクロージャを参照しています。asHTML プロパティは () -> String 型であり、または "パラメータを取らない、String 値を返す関数" です。


デフォルトでは、asHTML プロパティは、HTML タグの文字列表現を返すクロージャが割り当てられています。それが存在する場合、または全くテキストコンテンツの text が存在しない場合、このタグは、optional の text 値を含んでいます。段落要素については、クロージャは text プロパティが "some text"nil に等しいかどうかに応じて、"<p>some text</p>" または "<p />" を返します。


asHTML プロパティは、多少インスタンスメソッドのような名前を付けられ使用されています。しかし、asHTML は、インスタンスメソッドと言うよりクロージャプロパティであるため、特定の HTML 要素のための HTML レンダリングを変更したい場合は、カスタムのクロージャで asHTML プロパティのデフォルト値を置き換えることができます。


例えば、asHTML プロパティは、text プロパティが nil の場合、空の HTML タグを返す事から表現を防止するために、デフォルトを何らかのテキストにするクロージャに設定することができます。


  1. let heading = HTMLElement(name: "h1")
  2. let defaultText = "some default text"
  3. heading.asHTML = {
  4. return "<\(heading.name)>\(heading.text ?? defaultText)</\(heading.name)>"
  5. }
  6. print(heading.asHTML())
  7. // Prints "<h1>some default text</h1>"


注意: 要素が、実際にいくつかの HTML 出力対象の文字列値としてレンダリングされる必要がある場合にのみ必要とされるため asHTML プロパティは、遅延した(lazy) プロパティとして宣言されています。asHTML は遅延したプロパティであるという事実は、遅延したプロパティの初期化が完了し、self が存在することが知られた後までアクセスできないので、デフォルトのクロージャ内で self を参照できることを意味します。


HTMLElement クラスは、新しい要素を初期化するために、name 引数と(必要に応じて) text 引数を取る、単一のイニシャライザを提供します。クラスはまた、HTMLElement インスタンスが割り当て解除されたときにメッセージを印刷して表示するデイニシャライザも定義しています。


新しいインスタンスを作成し、印刷する HTMLElement クラスを使用する方法をここに示します。


  1. var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
  2. print(paragraph!.asHTML())
  3. // prints "<p>hello, world</p>"


注意: 上記の paragraph 変数は optionalHTMLElement として定義され、そのため強い循環参照の存在を実証するために、下記では nil に設定できます。


残念ながら、HTMLElement クラスは上に書いたように、HTMLElement インスタンスとそのデフォルトの asHTML 値に使用されたクロージャの間に強い循環参照を作成します。ここで、循環参照がどのように見えるかを示します:





インスタンスの asHTML プロパティは、そのクロージャへの強い参照を保持します。しかし、クロージャがその本体 (self.nameself.text を参照する方法として) 内で self を参照するので、クロージャは、self を キャプチャ し、それは HTMLElement インスタンスへ戻る強い参照を保持していることを意味します。強い循環参照は2つの間に作成されます。(クロージャ内の値をキャプチャする方法の詳細については、値のキャプチャ を参照してください。)


注意: クロージャが self を複数回参照しても、HTMLElement インスタンスへの1つの強い参照をキャプチャするだけです。


paragraph 変数を nil に設定し、HTMLElement インスタンスへの強い参照を壊す場合、HTMLElement インスタンスもそのクロージャのいずれも、強い循環参照のため、割り当て解除されません。


paragraph = nil



HTMLElement デイニシャライザ内のメッセージは印刷されず、それは HTMLElement インスタンスが割り当て解除されていないことを示しているのに注意してください。



クロージャの強い循環参照の解決


クロージャの定義の一部として キャプチャリスト を定義することで、クロージャとクラスインスタンス間の強い循環参照を解決できます。キャプチャリストは、クロージャの本体内の1つ以上の参照型をキャプチャする際に使用する規則を定義します。2つのクラスインスタンス間の強い循環参照と同じように、強い参照ではなく、各々のキャプチャされた参照を、弱い参照または所有されていない参照として宣言して下さい。弱い参照または所有されていない参照の適切な選択は、コードの色々な部分の間の関係に依存します。



注意: Swift ではクロージャ内で self のメンバーを参照する時はいつでも self.someProperty または self.someMethod() (somePropertysomeMethod()ではなく) と記述する必要があります。これは、間違って self をキャプチャすることがあるのを思い出すのを助けます。


キャプチャリストの定義


キャプチャリストの各項目は、クラスインスタンスへの参照 (self のように) と、weak または unowned キーワードとペアリングして、またはある値で初期化された変数 (delegate = self.delegate のように) とペアリングします。これらのペアリングは、カンマで区切って角括弧のペア内に記述されます。


クロージャのパラメータリストの前にキャプチャリストを配置し、戻り値の型が提供されている場合はそれを書きます:


  1. lazy var someClosure = {
  2. [unowned self, weak delegate = self.delegate]
  3. (index: Int, stringToProcess: String) -> String in
  4. // closure body goes here
  5. }


パラメータリストや戻り値の型が文脈から推測することができるのでクロージャがそれらを指定しない場合、クロージャの最初にキャプチャリストを配置し、in キーワードを続けます:


  1. lazy var someClosure: () -> String = {
  2. [unowned self, weak delegate = self.delegate!] in
  3. // closure body goes here
  4. }


弱い参照と所有されていない参照


クロージャとそれがキャプチャするインスタンスは常にお互いを参照する時、常に同時に割り当て解除され、キャプチャをクロージャ内で所有されていない参照として定義します。


逆に、キャプチャされた参照が将来のある時点で nil になる可能性がある場合、キャプチャを弱い参照として定義します。弱い参照は常に optional の型であり、参照するインスタンスが割り当て解除されると自動的に nil になります。これにより、クロージャの本体内でのそれらの存在を確認できます。


注意: キャプチャされた参照が nil になることが決してなければ、弱い参照としてではなく所有されていない参照として、常にキャプチャされます。


所有されていない参照は、上に示した クロージャの強い循環参照HTMLElement の例での強い循環参照を解決するために使用する適切なキャプチャメソッドです。ここで、循環参照を回避するため HTMLElement クラスをどう書くか、方法を示します。


  1. class HTMLElement {
  2. let name: String
  3. let text: String?
  4. lazy var asHTML: () -> String = {
  5. [unowned self] in
  6. if let text = self.text {
  7. return "<\(self.name)>\(text)</\(self.name)>"
  8. } else {
  9. return "<\(self.name) />"
  10. }
  11. }
  12. init(name: String, text: String? = nil) {
  13. self.name = name
  14. self.text = text
  15. }
  16. deinit {
  17. print("\(name) is being deinitialized")
  18. }
  19. }


この HTMLElement の実装は、asHTML クロージャ内へのキャプチャリストの追加を別にすれば、以前の実装と完全に同一です。この場合、キャプチャリストは、"強い参照ではなく、所有されていない参照として self をキャプチャする" を意味する、[unowned self] です。


以前のように HTMLElement インスタンスを作成し、印刷できます。


  1. var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
  2. print(paragraph!.asHTML())
  3. // prints "<p>hello, world</p>"


参照がキャプチャリストの所定の位置でどのように見えるかをここに示します:





今度は、クロージャによる self のキャプチャは所有されていない参照であり、それがキャプチャした HTMLElement インスタンス上では強い保持を維持しません。paragraph 変数からの強い参照を nil に設定した場合は、以下の例で、そのデイニシャライザのメッセージの印刷から分かるように、HTMLElement インスタンスは、割り当てが解除されます。


  1. paragraph = nil
  2. // prints "p is being deinitialized"


キャプチャリストの詳細については、キャプチャ・リスト を参照してください。



前:不透明な型 次:メモリの安全性
















トップへ












トップへ












トップへ












トップへ
目次
Xcode の新機能

Swift について
Swift と Cocoa と Objective-C
Swift Blog より

SwiftLogo
  • Swift 5.8 全メニュー


  • Swift へようこそ
  • Swift について
  • Swift 言語のガイド
  • Swift の基本
  • 基本演算子
  • 文字列と文字
  • コレクション型
  • フロー制御
  • 関数
  • クロージャ
  • 列挙型
  • 構造体とクラス
  • プロパティ
  • メソッド
  • サブスクリプト
  • 継承
  • 初期化
  • デイニシャライザ
  • Optional の連鎖
  • エラー処理
  • 同時実行
  • 型キャスト
  • ネストした型
  • 拡張機能
  • プロトコル
  • ジェネリック(汎用)
  • 不透明な型
  • 自動参照カウント
  • メモリの安全性
  • アクセス制御
  • 高度な演算子

  • 言語リファレンス

  • マニュアルの変更履歴













  • トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ












    トップへ