web-dev-qa-db-ja.com

クロージャは、変化する自己パラメータを暗黙的にキャプチャできません

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")!
        }
})

しかし、私はこのエラーを受け取っています

クロージャは、変化する自己パラメータを暗黙的にキャプチャできません

このエラーの内容がわからないため、解決策を検索しても役に立たない

47
coding_999

ショートバージョン

FirebaseRef.observeSingleEvent(of:with:)への呼び出しを所有する型は、ほとんどの場合値型(struct?)です。この場合、変化するコンテキストは@escapingクロージャーでselfを明示的にキャプチャしない場合があります。

簡単な解決策は、所有タイプを参照に一度更新することです(class)。


長いバージョン

Firebaseの observeSingleEvent(of:with:)メソッド は次のように宣言されます

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をキャプチャすると、エスケープ可能なクロージャーリテラルキャプチャが明示的に行われない限り(したがって不変)。

さて、これは重要なポイントです。 valueタイプ(例: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 ...
63
dfri

同期ソリューション

クロージャ内の値の型(struct)を変更する必要がある場合、これは同期的にのみ機能しますが、非同期呼び出しでは機能しません。

struct Banana {
    var isPeeled = false
    mutating func peel() {
        var result =  self

        SomeService.synchronousClosure { foo in
            result.isPeeled = foo.peelingSuccess
        }

        self = result
    }
}

それ以外の場合は、可変(したがってvar)コピーを提供することを除いて、値型で「変化する自己」をキャプチャすることはできません。

なぜ非同期ではないのですか?

これが非同期コンテキストで機能しない理由は、コンパイラエラーなしでresultを変更できますが、selfに変更結果を割り当てることはできません。それでも、エラーは発生しませんが、クロージャーがディスパッチされる前にメソッド(peel())が終了するため、selfは変更されません。

これを回避するには、コードの変更を試みて、非同期呼び出しが終了するのを待って非同期実行を同期実行に変更します。技術的には可能ですが、これはおそらく相互作用している非同期APIの目的を無効にしているので、アプローチを変更した方がよいでしょう。

structclassに変更することは技術的に適切なオプションですが、実際の問題には対処しません。この例では、現在class Bananaであるため、そのプロパティはw​​ho-knows-whenを非同期に変更できます。理解するのが難しいので、問題が発生します。 モデル自体の外部でAPIハンドラーを記述し、実行のフェッチが完了してモデルオブジェクトを変更する方が適切です。コンテキストがなければ、適切な例を挙げるのは困難です。 (OPのコードでself.imgが変更されているため、これはモデルコードであると考えられます。)

「非同期の腐敗防止」オブジェクトを追加すると役立つ場合があります

私はこれの行の中で何かを考えています:

  • BananaNetworkRequestHandlerはリクエストを非同期的に実行し、結果のBananaPeelingResultBananaStoreに報告します
  • BananaStoreは、peelingResult.bananaIDを探して、その内部から適切なBananaを取得します
  • banana.bananaID == peelingResult.bananaIDでオブジェクトを見つけたら、banana.isPeeled = peelingResult.isPeeledを設定し、
  • 最後に、元のオブジェクトを変更されたインスタンスに置き換えます。

必要な変更がアプリのアーキテクチャの変更を含む場合は特に、簡単な修正を見つけるための探求から、それは非常に簡単に関与する可能性があります。

16
ctietze

(検索から)このページに誰かがつまずき、protocol/protocol extensionを定義している場合、protocolclass boundとして宣言すると役立つ場合があります=。このような:

protocol MyProtocol: class
{
   ...
}
12

これを試すことができます!あなたを助けることを願っています。

struct Mutating {
    var name = "Sen Wang"

    mutating func changeName(com : @escaping () -> Void) {

        var muating = self {
            didSet {
                print("didSet")
                self = muating
            }
        }

        execute {
            DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + 15, execute: {
                muating.name = "Wang Sen"
                com()
            })
        }
    }

    func execute(with closure: @escaping () -> ()) { closure() }
}


var m = Mutating()
print(m.name) /// Sen Wang

m.changeName {
    print(m.name) /// Wang Sen
}
0
Must_Save_Jane