Swiftで遊ぼう! on Hatena

あしたさぬきblogでやってた初心者オヤジのiOSプログラミング奮闘記がHatenaに来ました

Swiftで遊ぼう! - 690 - Closures, Extensions, Protocols, Delegation, and ScrollView

2016年12月6日:メモリーマネージメントの復習

スタンフォード大学ポール先生の講座「Developing iOS 9 Apps with Swift - Free Course by Stanford on iTunes Uの講義7に入ります。OOPの核心部分、私にとって復習のはずですが、色々と勉強になりました。

まずモリー・マネージメントの話ですが、ARC(Automatic Reference Counting)のことです。Classタイプは何でもメモリーヒープでオブジェクトを扱いますが、リファレンスが自動的にナンバリングされて管理されています。

このARCに影響を与えるのが、「strong」「weak」「unowned」です。

  1. strong デフォルトで設定されていて、オブジェクトが作られた瞬間からヒープに存在し続けます。
  2. weak オプショナル型のオブジェクトはweakにすべきで、一時的に消滅した場合はnilになりヒープ領域から消失することができます。最も代表的なのが「IBOutlet」です。
  3. unowned オブジェクト間でメモリーサイクルが生じる場合、それを切るために使います。

これを確かめていきます。

最初に作ったCalculatorプロジェクトを使います

オブジェクト・ライブラリから、新しいUIViewControllerをストーリーボード上の既存のCalculator(ViewController)の横に「ドラッグ&ドロップ」します。今回はコードは用意せず、ジェネリックなまま使用します。

新しく設置したViewControllerの上にUIButtonを設置*1して、ボタンの名前を「Calculate!」に変えます。

イニシャルポインタを新しいViewControllerに移して、新しいViewControllerが選択された状態からメニューの「Editor -> Embeded In -> Navigation Controller」を選択して、ナビゲーションコントローラーを組み込みます。

次に「Calculate!」ボタンから「Ctrl + ドラッグ」してCalculatorをコードしたViewController*2でリリースして出てくるメニューから「Show」を選んでセグエを作ります*3

f:id:yataiblue:20161206104820j:plain

これでViewControllerの切り替えデモの準備ができました。

セグエで切り替え(showの場合)とは、新しいCalculatorのViewControllerが画面上のVIewControllerの上に作られて切り替わったように見えます。Backボタンを押したらCalcularorのViewControllerが消されて下にあるViewControllerに戻ったように見えます。

これをコンソールで確かめるためにViewControllerに次のコードを加えます。

クラス宣言の外にグローバル変数を宣言します。

var calculatorCount = 0

ViewController作られた時に必ず1度呼ばれるメソッドはviewDidLoad()なので、次のコードを加えます。

override func viewDidLoad() {
    super.viewDidLoad()
    calculatorCount += 1
    print("Loaded up a new Calculator (count = \(calculatorCount))")
}

そしてヒープから消され直前に呼ばれるメソッドはdeinitなので次のコードを加えます。

deinit {
    calculatorCount -= 1
    print("Calulator left the heap (count = \(calculatorCount))")
}

これでランします。「Calculate!」ボタンと「Back」ボタンを交互に押すと次のメッセージがコンソールに表示されます。

f:id:yataiblue:20160601180945j:plain

これでARCのデモの準備が整いました。

これをベースに「string]」「weak」「unowned」のプロパティの挙動を見ていきます。

モデル(M)のCalculatorBrainクラスにUnaryOperation(単項式)を加えるメソッドを用意します。

func addUnaryOperation(_ symbol: String, 
                      operation: @escaping (Double) -> Double) {
    operations[symbol] = Operation.unaryOperation(operation)
}

これをViewControllerで呼ぶのですが、viewDidLoad()メソッドに加えます。

override func viewDidLoad() {
    super.viewDidLoad()
    calculatorCount += 1
    print("Loaded up a new Calculator (count = \(calculatorCount))")
    brain.addUnaryOperation("Z") { [ weak weakSelf = self] in
        weakSelf?.display.textColor = UIColor.red
        return sqrt($0)
    }
}    

クロージャーで指定している「self」の付いたプロパティが「strong」になりアプリが存在し続ける限りヒープに残ってしまうんです。

これをランして、「Calculate!」ボタンと「Back」ボタンを交互に押すと、次のようにViewControllerが次々と作成されてしまいます。

f:id:yataiblue:20160602162621j:plain

これが所謂メモリーリークという現象ですね。これを避けるために次のように「unowned」を使うやり方があります。

override func viewDidLoad() {
    super.viewDidLoad()
    calculatorCount += 1
    print("Loaded up a new Calculator (count = \(calculatorCount))")
    brain.addUnaryOperation("Z") { [ unowned me = self ] in
        me.display.textColor = UIColor.redColor()
        return sqrt($0)
    }
}

「weak」は基本的にオプショナル扱いになるので次のようにオプショナルチェインを使って実装します。

override func viewDidLoad() {
    super.viewDidLoad()
    calculatorCount += 1
    print("Loaded up a new Calculator (count = \(calculatorCount))")
    brain.addUnaryOperation("Z") { [ weak weakSelf = self ] in
        weakSelf?.display.textColor = UIColor.redColor()
        return sqrt($0)
    }
}

この2つの実装はどちらもメモリーリークがありません。

f:id:yataiblue:20160602163456j:plain

ということで今日はこれだけ。

*1:コンストレイントは設定します。

*2:名前を変えていないので、ViewControllerのままです。

*3:使わないのでidentifierは設定しません。