Swift

Swift/メモリ管理

参照型データとARC

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によるメモリ管理

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

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

値型のデータの共有とコピー

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

強い参照の循環

インスタンスが解放できない場合

「あるインスタンスが他のインスタンスを参照する」という関係をたどって元のインスタンスが参照されるように なると、インスタンスが使われなくなっても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        // 何の表示もない
// つまり、メモリは解放されていない

弱い参照

通常の参照では 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 と表示される

非所有参照

「強い参照の循環」を防ぐ方法は「弱い参照」だけではなく、 「非所有参照 (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 と表示される

オプショナルチェーン

オプショナル型の値を連続利用する場合

オプショナル型の値を 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)
    }
  }
}

オプショナルチェーン

オプショナルチェーン (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)")
}

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

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

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

// who か who.club がnilの場合に add() や getNewComer() メソッドは実行されない
who?.club?.add( getNewcomer() )

オプショナルチェーンを使うと、delegate のように 「変数に有効な値が入っていればそれを呼び出すが、有効でなければ何もしない」 という処理が簡潔に記述できる。

delegate?.notify(event)   // delegate が nil ならば何もしない

オプショナルチェーンの型

オプショナルチェーンは 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と比較することで、メソッドが呼び出されたかどうか判定できる。

オプショナルチェーンに対する代入

who?.club?.teacher = someTeacher

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

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

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

トップ   編集 凍結 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS
Last-modified: 2016-06-10 (金) 12:12:03 (1455d)