#author("2016-06-10T02:22:22+00:00","default:nitta","nitta")
#author("2016-06-10T03:12:03+00:00","default:nitta","nitta")
[[Swift]]

*Swift/メモリ管理 [#ta37fb1f]

**参照型データとARC [#zff93b14]

Swift ではクラスのインスタンスとクロージャだけが参照型のデータであり、
プログラムの複数箇所から参照されることがある。
参照型のデータのメモリを解放してよいか判断するために、
Objective-C や Swift ではリファレンスカウンタ (reference counter) を用いている。

 class Person: CustomStringConvertible {
   let name: String
   var age: Int
   init(name: String, age: Int) { self.name = name; self.age = age }
   deinit { print("\(name): deinit") }
   var description: String { return "\(name), \(age)" }
 }
 var alice: Person! = Person(name: "Alice", age: 18)  (reference count = 1)
 print(alice)     // Alice, 18 と表示される
 var x:Person! = alice  // 参照をコピーする (reference count=2)
 x.age++
 print(alice)     // Alice, 19 と表示される
 print(alice === x)   // true と表示されるw
 alice =  nil    // 参照は減るがまだ0ではない (reference count = 1)
 x = nil         // Alice: deinit と表示される (reference count = 0なのでメモリが解放される)

Swift では、
インスタンスが全く同一かどうか(同じ実体を指しているか)を調べるには演算子 "===" または "!==" を使う。

** ARCによるメモリ管理 [#b65a4496]

Swift では reference counter は ARC (Automatic Reference Counting) が管理している。

Objective-Cとは異なり、Swiftでは配列や辞書は値型のデータである。
参照型データ(クラスのインスタンスやクロージャ)が
配列や辞書に格納されているときもreference counting はARCが正しく行う。

**値型のデータの共有とコピー [#je636d08]

Swiftでは関数の引数として配列などの値型のデータを渡すとき Copy-on-Write 方式をとる。
すなわち、最初にポインタだけを渡し、データに変更がない限りは共有する。
元のデータまたは渡した先で書き込みが発生した時点で、データの複製を作成する。

*強い参照の循環 [#k9f91e67]

**インスタンスが解放できない場合 [#o5dbaeaf]

「あるインスタンスが他のインスタンスを参照する」という関係をたどって元のインスタンスが参照されるように
なると、インスタンスが使われなくなってもreference countは0にはならないのでメモリが解放されない。
これを「強い参照の循環 (strong reference cycle)」または「循環参照」と呼ぶ。

 class Student: CustomStringCovertible {
   let name: String
   var club: Club?
   init(name:String) { self.name = name }
   dinit { print("\(name): deinit") }
   var description: String {
     var s = "\(name)"
     if let mem = club { s += ", \(mem.name)" }
     return s
   }
 }
 class Club {
   let name: String
   var members = [Student]()
   init(name: String) { self.name = name }
   deinit { print("Club \(name): deinit") }
   func add(p: Student) { members.append(p); p.club = self }
 }
 var club: Club! = Club("baseball")
 var alice: Student! = Student("Alice")
 club.add(alice)
 print(alice)      // Alice, baseball と表示される
 alice = nil       // 何の表示もない
 club = nil        // 何の表示もない
 // つまり、メモリは解放されていない

**弱い参照 [#sc57f05b]

通常の参照では reference counter の値を1増やすが、増やさない参照を作ることができて
これを「弱い参照 (weak reference)」と呼ぶ。
弱い参照を保持する変数は宣言時にキーワード weak をつける。
インスタンスが解放された時、弱い変数にはnilが自動的に代入される(この操作を「ゼロ化 (zeroing)」と呼ぶ)。

弱い参照を保持する変数はnilを保持する場合があるのでオプショナル型で宣言する。

 var bob: Student! = Student("Bob")
 weak var p: Student? = bob
 print(p!)       // Bob と表示される
 bob = nil       // Bob: deinit と表示される
 print(p)        // nil と表示される

 // 弱い参照を使ってStudentを再定義する
 class Student: CustomStringCovertible {
   let name: String
   weak var club: Club?             // weak 修飾子を追加した
   init(name:String) { self.name = name }
   dinit { print("\(name): deinit") }
   var description: String {
     var s = "\(name)"
     if let mem = club { s += ", \(mem.name)" }
     return s
   }
 }
 var club: Club! = Club("baseball")
 var alice: Student! = Student("Alice")
 club.add(alice)            // クラブに追加する
 print(alice)      // Alice, baseball と表示される
 club.add(Student("Bob"))   // もう一人クラブに追加する
 club = nil        // Club baseball: deinit
                   // Bob: deinit           と表示される
 alice = nil       // Alice: deinit と表示される

**非所有参照 [#j74b77da]
「強い参照の循環」を防ぐ方法は「弱い参照」だけではなく、
「非所有参照 (unowned reference)」 を使ってもよい。
「非所有参照 (unowned reference)」 では、クラスのインスタンスを参照しても
所有権を主張せず、reference countを増やさない。

非所有参照を保持する変数は宣言時にキーワード unowned をつける。


「非所有参照」が「弱い参照」と異なるのは、「何らかのインスタンスを常に参照し続ける」すなわち、
「nilを値とはしない」という点である。
参照していたインスタンスが解放されてもnilが自動的に代入されることはない。

 var alice: Student! = Student("Alice")
 unowned var p: Student = alice
 print(p)             // Alice と表示される
 alice = nil          // Alice: deinit と表示される
 print(p)             // エラーとなる

非所有参照はゼロ化操作が必要ないので、弱い参照よりも実行効率がよいと考えられる。

クラス Student を unowned キーワードを使って書き変えた例を示す。
「学生がクラブに所属していない場合や、廃部になってしまう場合は、
クラス Stundent の club プロパティは nil になることがありそうだ。
しかし、学生ならば必ずホームルーム (学級)に属していると考えると、
homeroomプロパティはunowned で宣言してもよさそうだ。」

 class Student: CustomStringCovertible {
   let name: String
   unowned var homeroom: HomeRoom
   init(name:String, hr: HomeRoom) { self.name = name; homeroom = hr }
   dinit { print("\(name): deinit") }
   var description: String { return "\(name), \(homeroom)" }
 }
 class HomeRoom {
   let name: String
   var members = [Student]()
   init(name: String) { self.name = name }
   deinit { print("HomeRoom \(name): deinit") }
   func add(p: Student) { members.append(p) }
 }
 var hr1: HomeRoom! = HomeRootm(name:"3A")
 var alice: Student! = Student("Alice", hr1)
 print(alice)             // Alice, 3A と表示される
 var hr2: HomeRoom! = HomeRootm(name:"3B")
 alice.homeroom = hr2
 hr1 = nil              // HomeRoom 3A: deinit と表示される
 alice = nil            // Alice: deinit と表示される
 hr2 = nil              // HomeRoom 3B: deinit と表示される
 
 

*オプショナルチェーン [#ma98e471]

**オプショナル型の値を連続利用する場合 [#c2a06351]

オプショナル型の値を unwrap して利用する場合、nilであるか否かのチェックが必要となる。
Swift では、オプショナル束縛構文、nil合体演算子、オプショナルチェーン
などを用いてチェックを記述する。

 class Student {
   let name: String
   weak var club: Club?
   init(name:String) { self.name = name }
 }
 class Teacher {
   let name: String
   var subject: String? = nil       // 担当科目
   init(name:String) { self.name = name }
 }
 class Club {
   let name: String
   weak var teacher: Teacher?          // 顧問 (弱い参照)
   var budget = 0                      // 予算
   var members = [Student]()
   init(name: String) { self.name = name }
   func add(p: Student) { members.append(p); p.club = self }
   func legal() -> Bool { return members.count >= 5 && teacher != nil } // 公認クラブか?
 }
 var club: Club! = Club("baseball")
 var alice: Student! = Student("Alice")
 club.add(alice)
 print(alice)      // Alice, baseball と表示される
 alice = nil       // 何の表示もない
 club = nil        // 何の表示もない

次のように記述すると、変数 who がnilの場合、学生がクラブに入っていない場合、
クラブに顧問が居ない場合は実行時エラーとなってしまう。
 who: Student?
 ...(略)
 print(who!.club!.teacher!.name)

オプショナル型の値が nil かどうかを愚直にif文で書き連ねると次のようになる。
 if who != nil {
   if who!.club != nil {
     if who!.club!.teacher =!= nil {
       print(who!.club!.teacher!.name)
     }
   }
 }

オプショナル束縛構文では unwrap の "!" が必要ないので見た目の複雑さは減るが、変数の意味を把握しておく必要が生じる。
 if let w = who {
   if let c = w.club {
     if let t = c.teacher {
       print(t.name)
     }
   }
 }

**オプショナルチェーン [#dc07c7a8]

オプショナルチェーン (optional chining) を使うと、上の例が簡潔に記述できる。

 if let name = who?.club?.teacher?.name {
   print(name)
 }

オプショナルチェーンでは、unwrap の "!" の代りに "?" を使い、オプショナル型を含む参照の経路を辿る記述をする。
「記述した経路に含まれるオプショナル型が全てnilでなく、最終的に nil でない値が得られる」場合に、
それをオプショナルチェーンの値とする。
経路の途中で nil となるとそれ以上の評価は行われず、オプショナルチェーン自体の値が nil となる。

 var studentCouncil = [ String: Student ] ()     // 生徒会の役員
 studentCouncil["会長"] = Student(name: "Alice")
 if let name = StudentCouncil["常任委員"]?.name {
   print("常任委員: \(name)")
 }

**オプショナルチェーンでメソッドを呼び出す [#zdf4fb94]

オプショナルチェーンの渡中にメソッド呼び出しを含めることができる。

オプショナルチェーンの評価は逐次的に進められるので、途中でnilになった場合は
評価が打ち切られるので注意が必要である。

 // who か who.club がnilの場合に add() や getNewComer() メソッドは実行されない
 who?.club?.add( getNewcomer() )
 
オプショナルチェーンを使うと、delegate のように
「変数に有効な値が入っていればそれを呼び出すが、有効でなければ何もしない」
という処理が簡潔に記述できる。
 delegate?.notify(event)   // delegate が nil ならば何もしない

**オプショナルチェーンの型 [#r7ad3088]

オプショナルチェーンは nil を返すことがあるので式の型はオプショナル型である。

 who: Stunden?
 ...
 var name = who?.club?.teacher?.name     // name は String? 型
 var budget = who?.club?.budget          // budget は Int? 型
 var recognized = who?.club?.legal()     // recognized は Bool? 型

legal()関数は Bool が返り値であるがオプショナルチェーンでnilが返る可能性があるので
変数 recognized の型は Bool? となる。

 var k = who?.club?.add(alice)
 if who?.club?.add(alice) != nil {
     /* add()が呼び出された場合 */
 }
 
値を返さないメソッドはVoid を返すとみなすので、
オプショナルチェーンでそのようなメソッドが呼び出された場合は
返り値は Void? となる。
このような返り値はnilと比較することで、メソッドが呼び出されたかどうか判定できる。

**オプショナルチェーンに対する代入 [#v2848042]

 who?.club?.teacher = someTeacher

オプショナルチェーンは代入先として指定することもできる。
式の評価が nil となった場合は代入は行われないが、
代入が行われない場合でも右辺の評価は行われる。

オプショナルチェーンに対する代入操作は Void? 型を返す。(Swift では代入は式ではなく値を返さないことに注意)。
次のように記述すると、代入操作が行われたかどうか判定できる。

 if (who?.club?.teacher = someTeacher) != nill { /* 代入が行われた場合 */ }
 if (who?.club?.budege += 300) != nill { /* 代入が行われた場合 */ }



トップ   編集 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS