Несоответствия заявления гвардии

// Выполнение let first с последующей проверкой bool в выражении guard приводит к ошибке компиляции

   self.action = {  [weak self] in
      guard let `self` = self, data.isJSON() else { return }

// Сначала выполняем проверку bool, а затем пусть работает

  self.action = {  [weak self] in
     guard data.isJSON(), let `self` = self else { return }

Два утверждения выше кажутся мне эквивалентными. Почему это не сработает в первом случае?

1 ответ

Перестройте ваш вопрос в минимальный, полный и проверяемый пример.

Прежде всего, обратите внимание, что было бы предпочтительно, если бы ваш вопрос содержал минимальный, полный и поддающийся проверке пример (mvce), который в своей текущей форме не имеет:

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

Вместо этого, ваш вопрос мог бы быть сконструирован так:

func foo(bar: Int?) {
    // 1. why does this cause a compile time error?
    guard let baz = bar, true else { return }

    // 2. whereas this does not?
    guard true, let bax = bar else { return }
}

Ответ ниже будет обсуждать этот mvce, а не скрытый пример в исходном вопросе. Наконец, отметим также, что guard / guard let утверждение не является полностью релевантным (уникальным) по отношению к сути вопроса, так как мы видим такое же поведение для if / if let заявления. Ответ ниже будет использовать guard заявления.


Теперь, можем ли мы погасить ошибку времени компиляции в 1. выше? И эти два утверждения действительно эквивалентны?

Ошибка времени компиляции для 1-го guard оператор в функции foo(...) выше довольно показательно

Булево условие требует where отделить его от привязки переменной.

Исправить это: заменить , с where

Об этом также говорится в руководстве по языку - Основы - Дополнительная привязка

Вы можете включить несколько дополнительных привязок в одном if Заявление и использовать where пункт, чтобы проверить на Boolean состояние Если любое из значений в необязательных привязках nil или where пункт оценивает false вся необязательная привязка считается неудачной.

Следовательно, если мы хотим использовать условие-условие после необязательного связывания в guard или же if заявление, вам нужно использовать where условие, чтобы отделить условие-предложение от предыдущей необязательной привязки.

func foo(bar: Int?) {
    // 1. ok, compiles
    guard let baz = bar where true else { return }

    /* 2. or, include a conditional-clause prior to the
          optional binding, but is this really equivalent..? */
    guard true, let bax = bar else { return }
}

Эти два, однако, не совсем эквивалентны;

  • утверждение 2. выше позволяет нам замкнуть guard заявление в случае, если начальная условная оговорка оказывается false (затем не переходя к необязательному связыванию, а непосредственно в else блок из guard заявление)
  • тогда как 1. выше допускает обратное: проверять условное предложение только в том случае, если успешное связывание выполнено успешно.

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

let heavyStuff: () -> Bool = { print("foo"); /* ... */ return true }

func foo(bar: Int?) {
    /* 1. call the heavyStuff boolean construct only if
          the optional binding succeeds */
    guard let baz = bar where heavyStuff() else { return }

    /* 2. possibly unnesessarily perform heavy boolean
          stuff prior to failing the optional binding */
    guard heavyStuff(), let bax = bar else { return }
}

Менее надуманный пример, если мы хотим использовать успешно связанную переменную в (здесь: в качестве аргумента) в условном предложении, которое следует

let integerStuff: (Int) -> Bool = { _ in /* ... */ return true }

func foo(bar: Int?) {
    /* 1. call the integerStuff boolean construct only if
          the optional binding succeeds, using the binded
          immutable as closure argument */
    guard let baz = bar where integerStuff(baz) else { return }

    /* 2. ... not really any good alternatives for such 
          flow if using this alternative */
    guard integerStuff(baz ?? 0), let bax = bar else { return }
}

Наконец, обратите внимание, что технически, если вы действительно хотите отделить начальную необязательную привязку с помощью следующего условного предложения, вы можете использовать пустышку case let (необязательное всегда успешное связывание / присваивание переменной) в сочетании с where ключевое слово для вашего условного предложения

let checkThis: () -> Bool = { /* ... */ return true }

func foo(bar: Int?) {
    // ...
    guard let baz = bar, case let _ = () where checkThis() else { return }
}

Это, однако, просто чтобы показать эту техническую значимость; на практике просто используйте where пункт.

Другие вопросы по тегам