kentana20 技忘録

技術ネタを中心に、セミナー、勉強会、書籍、会社での出来事を綴っていきます。不定期更新。

詳解Swift#6(Chapter9 メモリ管理)

前回に続き、詳解Swiftの写経を続ける。

詳解 Swift 改訂版

詳解 Swift 改訂版

chapter9 メモリ管理

Swiftでは

のみが参照型のデータ。多くの場合はメモリ管理を意識しなくてもよい(ホントか?)が、特定の状況下ではリファレンスカウンタはじめ、メモリ管理の概要を把握しておくべき、と書かれている。

参照型データとARC

  • リファレンスカウンタの概念
class Person : CustomStringConvertible {
    let name:String
    var age:Int
    init(name:String, age:Int) {
        self.name = name; self.age = age
    }
    var description: String {
        return "\(name), \(age)"
    }
    deinit {
        print("\(name): deinit")
    }
}

var yuta:Person! = Person(name:"勇太", age:16)
print(yuta)
var x:Person! = yuta
x.age++
print(yuta)
print(yuta === x)
yuta = nil // ここではリファレンスカウンタが 2 -> 1 になるだけなので、deinitされない
x = nil //ここでリファレンスカウンタが0となるので、 deinitされる

演算子の話: === 同一性を調べる、 !== 同一でないことを調べる。

  • ARC(アーク)
    • コンパイル時にリファレンスカウンタの増減タイミングを決定するメモリ管理方式
    • Objective-C では手動で管理していた

強い参照の循環

class Student : CustomStringConvertible {
    let name: String                       // 名前
    var club: Club?                        // 所属クラブ
    init(name:String) { self.name = name }
    var description: String {
        var s = "\(name)"
        if let mem = club { s += ", \(mem.name)" }
        return s
    }
    deinit {                               // デイニシャライザ
        print("\(name): deinit")
    }
}

class Club {
    let name: String                       // クラブ名
    var members = [Student]()              // 所属学生のリスト
    init(name:String) { self.name = name }
    func add(p:Student) {                  // 学生を追加する
        members.append(p)
        p.club = self                      // 学生の所属も書き換える
    }
    deinit {                               // デイニシャライザ
        print("Club \(name): deinit")
    }
}

var tinyClub: Club! = Club(name:"昼寝同好会")
var yuji:Student! = Student(name:"悠二")
tinyClub.add(yuji)
print(yuji)
yuji = nil // deinitされない
tinyClub = nil // deinitされない

インスタンスの循環参照が発生している場合はARCによってメモリ解放されない

  • 弱参照 / 強参照
    • 弱参照
      • weak 修飾子を変数の前に付ける
      • リファレンスカウンタの値に影響しない
    • 強参照
      • 通常の参照
      • リファレンスカウンタの値に影響する
参照の種類 定義の仕方 リファレンスカウンタへの影響 備考
弱参照 weak 修飾子を付与 なし オプショナル型である必要がある
弱参照 通常の変数呼び出し あり

まとめるとこんな感じですね。

  • 非所有参照

弱参照と同様にリファレンスカウンタに影響しない参照方法。 unowned 修飾子を付ける。型にオプショナルは指定できない。「変数の値がnilにならないことが明らかな場合」は弱参照ではなく、非所有参照を使う方が高速に動作し、Appleも推奨している。

var hg1: Hoge! = Hoge()
unowned var hg2: Hoge = hg1
print(hg2) // Hoge
hg1 = nil
//print(hg2) <- error

参照先が解放された後で参照すると実行時エラーとなる。

オプショナルチェーン

オプショナル型はプロパティやメソッドの返り値で、nilが返る可能性がある場合に使われる。オプショナル型の値を ! で開示して使用する場合、その値がnilなのかをチェックしなければいけない。

  • オプショナル束縛構文
    • if let x = オプショナル型 的なアレ
  • nil合体演算子
    • ?? で表す自己代入的なアレ

などの記述も用意されているが、オプショナルチェーンもその1つ。

  • オプショナルチェーン
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 who: Student? = Student(name: "kentana20")
//who!.club!.teacher!.name <- error

if let w = who {
    if let cl = w.club {
        if let tc = cl.teacher {
            print(tc.name)
        }
    }
}

// ↑をオプショナルチェーンで書くと↓

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

! による開示の代わりに ? を使って目的の値を記述する。どこかでnilが返る場合はオプショナルチェーンの値はnilとなる。「値がある場合とない場合で処理が異る場合」は重宝しそう。

var recognized = who?.club?.legal()

この場合、recognizedはBool?型(オプショナル)となる。

所感・雑感

  • 参照型のデータを扱う場合はリファレンスカウンタを意識しないといけない
  • 弱参照(weak) / 非所有参照(unowned) をうまく使えば参照循環を作らず、メモリにやさしい処理になる
  • オプショナルチェーンは宣言的に書けてよいが、値はオプショナル型であることに注意する