JY-CONTENTS

JY

JY-CONTENTS
search

+

MENU

TAG:

【Swift】クロージャの [weak self] について

【Swift】クロージャの [weak self] について

(DATE)

-

2019.12.27

(CATEGORY)

-

クロージャ内で「self」を使用した場合のインスタンスへの参照カウントを見ていきます。

class Sam{
    var num: Int
    private var closure1: (()->Void)?
    private var closure2: (()->Void)?
    
    init(num: Int) {
        self.num = num
    }
    
    deinit {
        print("deinit(解放) \(self.num)")
    }
    
    func normalFunc(){
        print("normalFunc: \(self.num)")
    }
    
    func closureInFunc01(){
        self.closure1 = {
            print("closureInFunc01: \(self.num)")
        }
        closure1!()
    }
    
    func closureInFunc02(){
        self.closure2 = { [weak self] in
            print("closureInFunc02: \(self!.num)")
        }
        closure2!()
    }
}


var ins1:Sam? = Sam(num: 1)
var ins2:Sam? = Sam(num: 2)
var ins3:Sam? = Sam(num: 3)

ins1!.normalFunc()
ins2!.closureInFunc01()
ins3!.closureInFunc02()

ins1 = nil
ins2 = nil
ins3 = nil


//--出力結果------
//normalFunc: 1
//closureInFunc01: 2
//closureInFunc02: 3
//deinit(解放) 1
//deinit(解放) 3

クラス内の関数

確認すべき挙動は「closureInFunc01」と「closureInFunc02」の2つの関数になります。

normalFunc

シンプルな出力するためだけの関数です。
その他関数との比較のために設けています。

closureInFunc01

メンバ変数「closure1」にクロージャを代入し、実行します。
クロージャ内に「self」を使用しているためクロージャが「self」を保有(参照)する事になります。

closureInFunc02

closureInFunc01関数と内容は同じですが、メンバ変数「closure2」に代入するクロージャのselfに「weak」キーワードを指定しています。

deinit

インスタンスが解放されたかどうかの確認のために設けています。

インスタンスの生成

3つのインスタンスを生成し、比較していきます。

var ins1:Sam? = Sam(num: 1)
var ins2:Sam? = Sam(num: 2)
var ins3:Sam? = Sam(num: 3)

インスタンスを生成した時点での参照カウントはそれぞれ以下のようになります。

  • 変数ins1がインスタンスを保有(参照)。
    変数ins1に格納されたPersonインスタンスの参照カウント「1」
  • 変数ins2がインスタンスを保有(参照)。
    変数ins2に格納されたPersonインスタンスの参照カウント「1」
  • 変数ins3がインスタンスを保有(参照)。
    変数ins3に格納されたPersonインスタンスの参照カウント「1」

この時点では全てのインスタンスの参照カウントは「1」です。

インスタンス変数から関数を呼び出してみる

生成した3つのインスタンス変数から、それぞれ違う関数を呼び出して挙動を比較、確認してみます。

ins1!.normalFunc() //normalFunc: 1

ただ出力するだけの関数の呼び出しなので、インスタンスの参照カウントは「1」のままです。

ins2!.closureInFunc01() //closureInFunc01: 2

closureInFunc01関数は、メンバ変数closure1にself(インスタンス)を参照したクロージャを代入しています。
なので、この時インスタンス(self)は変数ins2とメンバ変数closure1に代入されたクロージャから参照(強参照)されています。
よってインスタンスの参照カウントは「2」となります。

ins3!.closureInFunc02() //closureInFunc02: 3

この関数の仕事はclosureInFunc01関数と同じです。
ただ、メンバ変数closure2に代入されたクロージャはselfにweakキーワードを指定しているのでselfへの参照が弱参照になります。
なので、参照カウントはされません。
よってインスタンスの参照カウントは「1」のままとなります。

それぞれのインスタンスは解放されるかの確認

それぞれのインスタンス変数に nil を代入して、インスタンスが解放されるか確認してみます。

ins1 = nil

インスタンスへの参照はインスタンス変数ins1だけなので、その変数にnilを代入する事でインスタンスの参照カウントは「0」になるのでインスタンスは解放されます。
インスタンスは解放されたので、deinit関数が呼び出されて「deinit(解放) 1」が出力されます。

ins2 = nil

インスタンスへの参照はインスタンス変数ins2とメンバ変数closure1に代入されたクロージャからなので、インスタンス変数ins2にnilを代入しても参照カウントは「1」で「0」にはらならずインスタンスは解放されません。
よってdeinit関数は呼び出されません。

ins3 = nil

インスタンスへの参照はインスタンス変数ins3だけなので、その変数にnilを代入する事でインスタンスの参照カウントは「0」になるのでインスタンスは解放されます。
インスタンスは解放されたので、deinit関数が呼び出されて「deinit(解放) 3」が出力されます。

コメントアウトで説明

以下コードに直接説明を記述しています。

class Sam{
    var num: Int
    private var closure1: (()->Void)?
    private var closure2: (()->Void)?
    
    init(num: Int) {
        self.num = num
    }
    
    deinit {
        print("deinit(解放) \(self.num)")
    }
    
    func normalFunc(){
        print("normalFunc: \(self.num)")
    }
    
    func closureInFunc01(){
        //変数にクロージャを格納
        //クロージャ内に「self」を使用しているためクロージャが「self」を保有(参照)する事になる
        self.closure1 = {
            print("closureInFunc01: \(self.num)")
        }
        closure1!()
    }
    
    func closureInFunc02(){
        //変数にクロージャを格納
        //クロージャ内に「self」を使用しているが「weak」キーワードを指定しているので
        //クロージャの「self」への参照は弱参照になる
        self.closure2 = { [weak self] in
            print("closureInFunc02: \(self!.num)")
        }
        closure2!()
    }
}

//変数ins1がインスタンスを保有(参照)。変数ins1に格納されたPersonインスタンスの参照カウント「1」
var ins1:Sam? = Sam(num: 1)
//変数ins2がインスタンスを保有(参照)。変数ins2に格納されたPersonインスタンスの参照カウント「1」
var ins2:Sam? = Sam(num: 2)
//変数ins3がインスタンスを保有(参照)。変数ins3に格納されたPersonインスタンスの参照カウント「1」
var ins3:Sam? = Sam(num: 3)


//この時点では全てのインスタンスの参照カウントは「1」です


//ただ出力するだけの関数の呼び出しなので、インスタンスの参照カウントは「1」のまま
ins1!.normalFunc()

//変数closure1にself(インスタンス)を参照したクロージャを代入している。
//この時インスタンス(self)は変数ins2と変数closure1に代入されたクロージャから参照(強参照)されている
//よってインスタンスの参照カウントは「2」となる。
ins2!.closureInFunc01()

//変数closure2にself(インスタンス)を参照したクロージャを代入している。
//この時インスタンス(self)は変数ins2と変数closure2に代入されたクロージャから参照(強参照)されているが
//変数closure2に代入されたクロージャはselfにweakキーワードを指定しているのでselfへの参照が弱参照になるため
//参照カウントはされない。よってインスタンスの参照カウントは「1」のままとなる。
ins3!.closureInFunc02()

//インスタンスへの参照はインスタンス変数ins1だけなので、その変数にnilを代入する事でインスタンスの
//参照カウントは「0」になるのでインスタンスは解放されます。
ins1 = nil

//インスタンスへの参照はインスタンス変数ins2と変数closure1に代入されたクロージャからなので
//インスタンス変数ins2にnilを代入しても参照カウントは「1」で「0」にはらならずインスタンスは解放されない。
ins2 = nil

//インスタンスへの参照はインスタンス変数ins3だけなので、その変数にnilを代入する事でインスタンスの
//参照カウントは「0」になるのでインスタンスは解放されます。
ins3 = nil



//--出力結果------
//normalFunc: 1
//closureInFunc01: 2
//closureInFunc02: 3
//deinit(解放) 1
//deinit(解放) 3

実用的に使ってみる

実際使いそうな感じなコードを書いてみます。
以下はSamクラスの定数の値の加工処理を別のクラス(Sam2)にクロージャを使用して記述しているコードです。
今回は税込価格を計算、出力させています。

class Sam{
    //渡された数値を何らかの加工する事を想定したクロージャ
    var closure: ((Double) -> Double)?
    let potechi = 100.0
    let choko = 120.0
    let ame = 80.0
    
    //クロージャを実行する関数
    func runClosure() -> Double{
        let total = potechi + choko + ame
        return self.closure!(total)
    }
}

class Sam2{
    let zeiritsu = 0.1
    let sam = Sam()
    
    //closure変数に税込価格を計算して返す内容のクロージャを代入し、実行
    func zeikomi(){
        sam.closure = { [weak self] total in
            let syouhizei = total * self!.zeiritsu
            return total + syouhizei
        }
        print("税込価格:\(Int(sam.runClosure()))円")
    }
    
    deinit {
        print("解放")
    }
}

var sam2: Sam2? = Sam2()
sam2!.zeikomi()
sam2 = nil


//---実行結果------
//税込価格:330円
//解放

インスタンス変数sam2よりzeikomi関数を実行した際、Sam2クラス内で生成したSamクラスのインスタンスのclosure変数にクロージャが代入されるので、Sam2クラス内で生成したSamクラスのインスタンスのclosure変数はself(インスタンス)を参照(強参照)します。
この時点でインスタンス変数sam2に代入されているインスタンスの参照カウントは「2」になりますが、クロージャのselfにweakキーワードを指定しているのでSam2クラス内で生成したSamクラスのインスタンスのclosure変数のselfへの参照は弱参照になり、参照カウントはカウントされず参照カウントは「1」のままです。
よってインスタンスが解放されているのがわかります。
weakキーワードを指定していなければ「解放」は出力されません。

以下のように「sam.closure = nil」を追加すればweakキーワードを指定していなくてもインスタンスは解放されます。

func zeikomi(){
     sam.closure = { total in
         let syouhizei = total * self.zeiritsu
         return total + syouhizei
     }
     print("税込価格:\(Int(sam.runClosure()))円")
     sam.closure = nil //追加
}

税込価格:330円
解放

上記の結果からもSam2クラス内で生成したSamクラスのインスタンスのclosure変数はself(インスタンス)を参照(強参照)していることがわかります。

NEW TOPICS

/ ニュー & アップデート

SEE ALSO

/ 似た記事を見る

JY CONTENTS UNIQUE BLOG

search-menu search-menu