#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 { /* 代入が行われた場合 */ }