1. ホーム
  2. ios

クロージャは変異する自己パラメータを暗黙のうちに捕らえることはできない

2023-09-07 16:29:14

質問

Firebaseを使ってイベントを監視し、補完ハンドラ内に画像を設定しています。

FirebaseRef.observeSingleEvent(of: .value, with: { (snapshot) in
        if let _ = snapshot.value as? NSNull {
            self.img = UIImage(named:"Some-image")!
        } else {
            self.img = UIImage(named: "some-other-image")!
        }
})

しかし、このようなエラーが発生します。

クロージャは変異する自己パラメータを暗黙的に捕捉することはできない

このエラーの意味がよくわからず、解決策を探しても解決しません

どのように解決するのですか?

ショートバージョン

への呼び出しを所有するタイプは FirebaseRef.observeSingleEvent(of:with:) は値型である可能性が高いです( struct を明示的にキャプチャしない場合があります。 self@escaping のクロージャになります。

単純な解決策は、所有する型を一度参照に更新することです( class ).


ロングバージョン

この observeSingleEvent(of:with:) というメソッドがあり、Firebaseの は次のように宣言されています。

func observeSingleEvent(of eventType: FIRDataEventType, 
     with block: @escaping (FIRDataSnapshot) -> Void)

block クロージャは @escaping パラメータ属性でマークされており、関数のボディをエスケープすることができます。 self の寿命さえも逃がしてしまう可能性があるということです (あなたの文脈では)。この知識を使って、より最小限の例を構築し、分析することができます。

struct Foo {
    private func bar(with block: @escaping () -> ()) { block() }

    mutating func bax() {
        bar { print(self) } // this closure may outlive 'self'
        /* error: closure cannot implicitly capture a 
                  mutating self parameter              */
    }
}

さて、エラーメッセージがより分かりやすくなったので、Swift 3で実装された以下の進化案に目を向けてみます。

を述べる[強調]。

を捕捉する inout パラメータを を含む self を含む、変異する メソッド は、エスケープ可能なクロージャリテラルではエラーになります。 になります。 キャプチャが明示されていない限り (そしてそれによって不変)となります。

さて、ここが重要なポイントです。に対して の値 型(例えば struct への呼び出しを所有する型) にも当てはまると思います。 observeSingleEvent(...) への呼び出しを所有する型にも当てはまると思います。この例では、そのような明示的なキャプチャは可能ではありません。

この問題に対する最も簡単な解決法は observeSingleEvent(...) を参照型にすることです。 class よりも、むしろ struct :

class Foo {
    init() {}
    private func bar(with block: @escaping () -> ()) { block() }

    func bax() {
        bar { print(self) }
    }
}

ただ、この場合 self を強い参照で捕捉してしまいます。コンテキストによっては (私自身は Firebase を使ったことがないのでわかりませんが)、明示的に self を弱く捕捉したいかもしれません。

FirebaseRef.observeSingleEvent(of: .value, with: { [weak self] (snapshot) in ...