26
19

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

[Swift] guard let self = self else { return } って何?

Last updated at Posted at 2020-07-15

##じょ
某所で好評を得ているのでこちらに転載します。

これはなに?

以下のようなよくあるサンプルコードのguard let self = self else { return }が何のためにあるのかよくわからない、という疑問にお答えします。

func didTapSignUpButton() {
    Auth.auth().createUser(withEmail: email, password: password) { [weak self] result, error in
        guard let self = self else { return }
        if let user = result?.user {
            // do something
        }
    }
}

短い回答

[weak self] でキャプチャしたselfがクロージャ実行時に存在しているかどうかを確認しています。

長い回答

guard let else

まず、

guard let xxx = yyy else { return }

yyyがOptionalで、yyyに値がなければreturnして値があればxxxにその値を入れる、という構文です。

例えば以下のように動きます。

func hoge(_ value: Int?) {
   gurad let v = value else { return }
   print(v)
}

hoge(10)  // prints 10
hoge(nil) // no prints

###変数のキャプチャ
次に、関数による変数のキャプチャというものがあります。

var val = 0

func fuga() {
   val += 2   //  valをキャプチャ
}

print(val)  // prints 0.

fuga()
print(val)  // prints 2.

関数にキャプチャされた変数valは関数内で値を変更することが可能です。

Swiftでは関数とクロージャは同等のものですので以下でも同じです。

var val = 0

let fuga = { val += 2 }

###インスタンスのオーナーシップ
最後にクラスのインスタンスのオーナーシップです。
クラスのインスタンスにはデータ領域がありますので、そのインスタンスを使わなくなった時にその領域が解放されなければメモリがひっ迫してしまいます。
しかし、使用中に開放されてしまうと、アプリケーションがクラッシュする事態になります。
このためSwiftではインスタンスにオーナーシップというものを設定しこれに対処しています。
原則として、使用中はオーナーを設定し使用が終わるとそれを外します。オーナーがすべてなくなった段階でインスタンスは解放されます。
これらは自動的に行われており、通常はプログラマは何も考える必要もありません。

ただし、循環参照という状態になるとインスタンスが解放されなくなるため、これを対処する必要があります。

class A {
  let b: B
  
  init(b: B) {
    self.b = b
    b.a = self
  }
}

class B {
   var a: A?
}

let a: A? = A(b: B())

この例は意味のあるコードではありませんが、循環参照の典型的な例です。
class Aのインスタンスはclass Bのインスタンスを、class Bのインスタンスはclass Aのインスタンスを参照しています。
そのため、上の例でa = nilとしてもa, bどちらのオーナーもなくならないため、どちらも解放されません。

この循環参照を回避するためにweakという修飾子が用意されています。
上の例ではclass Bの変数aweakとします。

class B {
   weak var a: A?
}

このweakという修飾子は「インスタンスは参照するがオーナーとしては登録するな。またインスタンスが解放されたとき値をnilにせよ。」という指示を与えます。
似た修飾子にunownedというものがあります。最後に少し説明をします。

こうすることでa = nilが実行されると、aのオーナーがすべてなくなりaが解放され、それによってbのオーナーもなくなるためbも解放されるようになります。

###キャプチャによるオーナシップの獲得
まだまだ続きます。

つぎは今までできたことの複合技、キャプチャによるオーナシップの獲得です。

これは名前の通りです。先ほどのclass Aを使います。

var a: A? = A(b: B())

let f = { print(a) }  // インスタンスaをキャプチャ

a = nil // インスタンスaは解放されない

クロージャがインスタンスaをキャプチャした時、自動的にオーナーが設定されるため、a = nilを実行してもインスタンスaは解放されません。ただしこの場合は、fが使われなくなった時にfと一緒にaも解放されます。

これにも循環参照の問題が発生します。

class C {
  var f: (() -> Void)?
}

let c: C? = C()

c?.f = { print(c) }

c = nil  // c は解放されない

このようにインスタンスcが持つクロージャがcをキャプチャしてしまったため、循環参照が発生し、cが解放されなくなってしまいます。
この時もweak修飾子を使います。ただし、クロージャ自体をweak参照することができないため以下のように行います。

c?.f = { [weak c] in print(c) }

このweakcをキャプチャする時にオーナーを設定するなという指示になります。
この指定をすることでc = nilの時にインスタンスcが解放されるようになります。

ところが、これでも問題が発生する場合があります。class Aを使います。

var a: A? = A(b: B())

let f = { [weak a] in print(a) }

a = nil

f()  // prints nil

aが解放された後にクロージャfを実行しようとすると、クロージャ内で使用しているaがすでに存在していないためnilになってしまいます。

###キャプチャしたインスタンスの存在確認

この問題を解決するためには実行前にインスタンスaが存在することを確認する必要があります。
クロージャfを以下のようにします。

let f = { [weak a] in
    gurad let a = a else { return }
    print(a)
}

まず、gurad let a = a else { return }でインスタンスaの存在を確認し、その後それを使用するようにします。

冒頭のサンプルコードにあるguard let self = self else { return }も同じことです。
[weak self]でキャプチャしたselfがクロージャ実行時に存在しているかどうかを確認しています。

##おまけ

キャプチャ時のweak指定の副作用

すっかりだまされいるようですね。
書いてあることは正しいのですが、実はguard let self = self else { return }についての説明が足りてません。

guard let xxx = yyy else { return }

yyyがOptionalで、yyyに値がなければreturnして値があればxxxにその値を入れる、という構文です。

と書きましたが、guard let self = self else { return }の右のselfはOptional型ではないはずです。

ところが、

var a: A? = A(b: B())

let f = { [weak a] in print(a) }

これを

let a = A(b: B())

let f = { [weak a] in print(a) }

としたとしても、クロージャ内のaA型ではなくOptional<A>型になります。
これがキャプチャ時のweak指定の副作用です。
weakは「(略)解放されたとき値をnilにせよ」という指示なのでOptional型じゃないと無理なんです。
なので強制的にOptional型になります。

unowned

「循環参照を避けたいけど、weakだとOptionalになって面倒くさい! ここでは絶対インスタンスは存在してるのに!!!」
という時に使うのがunownedです。

weak修飾子は「インスタンスは参照するがオーナーとしては登録するな。またインスタンスが解放されたとき値をnilにせよ。」という指示でしたが、unownedは「インスタンスは参照するがオーナーとしては登録するな」だけの指示を与えます。
nilにする必要がないため、キャプチャ時に強制的にOptional型になることはありません。
アンラップを使うことなくそのまま利用できますので便利です。

ただし、当然のことながらunownedはオーナーを設定しないため利用しようとしたときにインスタンスが解放されてしまっていることがあります。
解放されたインスタンスにアクセスするとプログラムがクラッシュします。
unownedを使う場合はそのことに十分注意しましょう。

26
19
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
26
19

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?