Подтвердить что ты не робот

Закрытие не может неявно захватывать параметр мутации

Я использую 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")!
        }
})

Однако я получаю эту ошибку

Закрытие не может неявно захватывать параметр mutating self

Я не уверен, что эта ошибка и поиск решений не помогли

4b9b3361

Ответ 1

Краткая версия

Тип, владеющий вашим вызовом FirebaseRef.observeSingleEvent(of:with:), скорее всего, является типом значений (a 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(...) в вашем примере, такой явный захват невозможен, afaik (поскольку мы работаем со значением тип, а не ссылочный).

Самое простое решение этой проблемы - сделать тип, которому принадлежит observeSingleEvent(...) ссылочный тип, например. a class, а не a 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 ...

Ответ 2

Sync Solution

Если вам нужно изменить тип значения (struct) в закрытии, это может работать только синхронно, но не для асинхронных вызовов, если вы пишете это следующим образом:

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

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

        self = result
    }
}

Вы не можете иначе захватить "мутирующую себя" со значениями типов, за исключением предоставления изменяемой (следовательно, var) копии.

Почему не Async?

Причина, по которой это не работает в асинхронных контекстах, такова: вы все равно можете мутировать result без ошибки компилятора, но вы не можете назначить мутированный результат на self. Тем не менее, ошибки не будет, но self никогда не изменится, потому что метод (peel()) завершается до того, как закрытие даже отправлено.

Чтобы обойти это, вы можете попытаться изменить свой код, чтобы изменить асинхронный вызов на синхронное выполнение, ожидая его завершения. Хотя это технически возможно, это, вероятно, побеждает цель асинхронного API, с которым вы взаимодействуете, и вам будет лучше сменить подход.

Изменение struct до class является технически обоснованной опцией, но не решает реальной проблемы. В нашем примере, теперь, будучи class Banana, его свойство может быть изменено асинхронно who-know-when. Это вызовет проблемы, потому что это трудно понять. Вам лучше писать обработчик API за пределами самой модели и после завершения выборки и изменить объект модели. Без дополнительного контекста трудно привести пример. (Я предполагаю, что это код модели, потому что self.img мутируется в OP-коде.)

Добавление объектов "асинхронный антикоррупционный" может помочь

Я думаю о чем-то среди строк этого:

  • a BananaNetworkRequestHandler выполняет асинхронные запросы, а затем возвращает результат BananaPeelingResult обратно в BananaStore
  • BananaStore затем берет соответствующий Banana изнутри, ища peelingResult.bananaID
  • Найдя объект с banana.bananaID == peelingResult.bananaID, он устанавливает banana.isPeeled = peelingResult.isPeeled,
  • наконец, заменив исходный объект мутированным экземпляром.

Вы видите, что из квеста, чтобы найти простое исправление, он может легко втянуться, особенно если необходимые изменения включают в себя изменение архитектуры приложения.

Ответ 3

Если кто-то наткнулся на эту страницу (из поиска) и вы определяете protocol/protocol extension, то может быть полезно, если вы объявите свой protocol как связанный с классом. Как это:

protocol MyProtocol: class {
   ...
}

Ответ 4

Вы можете попробовать это! Я надеюсь помочь вам.

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
}

Ответ 5

Другим решением является явное захват себя (поскольку в моем случае я был в мутирующей функции расширения протокола, поэтому я не мог легко указать, что это ссылочный тип).

Итак, вместо этого:

functionWithClosure(completion: { _ in
    self.property = newValue
})

У меня есть это:

var closureSelf = self
functionWithClosure(completion: { _ in
    closureSelf.property = newValue
})

Который, кажется, отключил предупреждение.

Обратите внимание, что это не работает для типов значений, поэтому, если self является типом значения, вам нужно использовать обертку ссылочного типа, чтобы это решение работало.