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

Быстрая опциональная цепочка не работает при закрытии

Мой код выглядит так. Мой класс имеет необязательный параметр var

var currentBottle : BottleLayer?

У BottleLayer есть метод jiggle().

Этот код, используя необязательную цепочку, компилируется в моем классе:

self.currentBottle?.jiggle()

Теперь я хочу построить замыкание, которое использует тот же код:

let clos = {() -> () in self.currentBottle?.jiggle()}

Но я получаю ошибку компиляции:

Не удалось найти элемент 'jiggle'

В качестве обходного пути я могу принудительно развернуть

let clos = {() -> () in self.currentBottle!.jiggle()}

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

Возможно, кто-то другой ударил в это и имеет мысли об этом? Спасибо.

4b9b3361

Ответ 1

Это НЕ ошибка. Это просто ваш тип закрытия, который является неправильным. Правильный тип должен возвращать необязательный Void, чтобы отразить дополнительную цепочку:

let clos = { ()->()? in currentBottle?.jiggle() }

Проблема в деталях:

  • Вы объявляете свое закрытие замыканием, которое возвращает Void (а именно ->()).
  • Но помните, что, как и каждый раз, когда вы используете необязательную цепочку, возвращаемый тип всего выражения необязательного типа. Поскольку ваше закрытие может возвращать Void, если currentBottle существует... или nil, если это не так!

Итак, правильный синтаксис состоит в том, чтобы сделать ваше закрытие возвратом Void? (или ()?) вместо простого Void

class BottleLayer {
    func jiggle() { println("Jiggle Jiggle") }
}
var currentBottle: BottleLayer?
currentBottle?.jiggle() // OK
let clos = { Void->Void? in currentBottle?.jiggle() } // Also OK
let clos = { () -> ()? in currentBottle?.jiggle() } // Still OK (Void and () are synonyms)

Примечание. Если вы позволили Swift вывести правильный тип для вас вместо того, чтобы явно его принудительно использовать, это устранило бы проблему для вас:

// Even better: type automatically inferred as ()->()? — also known as Void->Void?
let clos = { currentBottle?.jiggle() }

[EDIT]

Дополнительный трюк: непосредственно назначить необязательную цепочку переменной

Вы можете даже назначить функцию непосредственно переменной, например:

let clos2 = currentBottle?.jiggle // no parenthesis, we don't want to call the function, just refer to it

Обратите внимание, что тип clos2 (который явно не указан здесь и поэтому автоматически вызывается Swift) в этом случае не Void->Void? - а именно функция, которая возвращает либо nil или Void), как в предыдущем случае, но - (Void->Void)?, что является типом для необязательной функции типа Void->Void ".

Это означает, что clos2 сам "либо nil, либо функция возвращает Void". Чтобы использовать его, вы можете снова использовать необязательную цепочку, просто так:

clos2?()

Это будет оцениваться как nil и ничего не делать, если clos2 сам nil (вероятно, потому что currentBottle сам по себе nil)... и выполняет закрытие - таким образом, код currentBottle!.jiggle() - и возвращает Void если clos2 не-ниль (вероятно, потому что сам currentBottle не равен нулю).

Тип возврата clos2?() сам действительно Void?, так как он возвращает ниль или Void.

Выполнение разницы между Void и Void? может показаться бессмысленным (в конце концов, функция jiggle ничего не возвращает в любом случае), но она позволяет вам делать такие мощные вещи, как тестирование Void? в if, чтобы проверить, действительно ли был вызван вызов (и возвращен Void а именно ничего) или не произошло (и возвращает nil):

if clos2?() { println("The jiggle function got called after all!") }

[EDIT2] Как вы (@matt) указали на себя, этот другой вариант имеет еще одно важное отличие: он оценивает currentBottle?.jiggle в то время, когда это выражение повлияло на clos2. Поэтому, если currentBottle nil в это время, clos2 будет nil... даже если currentBottle получил значение, отличное от нуля.

И наоборот, clos затрагивается самой закрытием, а необязательная цепочка оценивается только каждый раз, когда вызывается clos, поэтому она будет оценивать до nil, если currentBottle равна nil... но будет оцениваться как non-nil и вызывается jiggle(), если мы назовем clos() в более позднее время, когда точка currentBottle стала не нулевой.

Ответ 2

let closure = { () -> () in
    self.currentBottle?.jiggle()
    return
}

В противном случае компилятор считает, что результат этого оператора должен быть возвращен из замыкания, и он понимает, что существует несоответствие между () (возвращаемым типом) и необязательным значением, возвращаемым оператором (Optional<Void>). Добавив явный return, компилятор будет знать, что мы ничего не хотим возвращать.

Конечно, сообщение об ошибке неверно.

Ответ 3

Хорошо, другой подход. Это связано с тем, что замыкания в Swift имеют неявные возвращения из закрытий с одним выражением. Из-за дополнительной цепочки у вашего закрытия есть тип возврата Void?, поэтому:

let clos = {() -> Void? in self.currentBottle?.jiggle()}

Ответ 4

Считая, что тип замыкания выведен, также работает.

let clos2 = { currentBottle?.jiggle() }
clos2() // does a jiggle

Но я уверен, что это просто компилятор, назначающий тип () -> ()?