Swift необязательный параметр Escapeing Closure

162

Дано:

typealias Action = () -> ()

var action: Action = { }

func doStuff(stuff: String, completion: @escaping Action) {
    print(stuff)
    action = completion
    completion()
}

func doStuffAgain() {
    print("again")
    action()
}

doStuff(stuff: "do stuff") { 
    print("swift 3!")
}

doStuffAgain()

Есть ли способ сделать completionпараметр (и action) типа, Action?а также сохранить @escaping?

Изменение типа приводит к следующей ошибке:

Атрибут @escaping применяется только к типам функций

При удалении @escapingатрибута код компилируется и запускается, но, по-видимому, он не является правильным, поскольку completionзамыкание выходит за рамки функции.

Lescai Ionel
источник
21
«Удаление @escapingатрибута, код компилируется и работает» - это потому , что, как описано в SR-2444 , Action?является, по умолчанию, убегая. Таким образом, удаление @escapingпри использовании дополнительного закрытия завершает то, что вам нужно.
Роб
закрытие псевдонимов типа
Masih
Вот отличная статья Оле Бегеманна, в которой описывается, почему это происходит, и некоторые обходные пути, если вы хотите, чтобы необязательные параметры были @noescape.
Чувствительный

Ответы:

122

Существует отчет SR-2552, который @escapingне распознает псевдоним типа функции. вот почему ошибка @escaping attribute only applies to function types. Вы можете обойти это, расширив тип функции в сигнатуре функции:

typealias Action = () -> ()

var action: Action? = { }

func doStuff(stuff: String, completion: (@escaping ()->())?) {
    print(stuff)
    action = completion
    completion?()
}

func doStuffAgain() {
    print("again")
    action?()
}

doStuff(stuff: "do stuff") {
    print("swift 3!")
}

doStuffAgain()

РЕДАКТИРОВАТЬ 1 :

Я был на самом деле под бета-версии xcode 8, где ошибка SR-2552 еще не была решена. исправляя эту ошибку, представил новую (ту, с которой вы столкнулись), которая все еще открыта. см. SR-2444 .

Обходной путь @Michael Ilseman указал, что временное решение - удалить @escapingатрибут из необязательного типа функции, который сохраняет функцию как экранирующую .

func doStuff(stuff: String, completion: Action?) {...}

РЕДАКТИРОВАТЬ 2 :

SR-2444 было закрыто , конкретно указав , что закрытие позиций в параметрах не избежать , и нужно , чтобы они были помечены , @escapingчтобы их избежать, но необязательные параметры будут неявно избежать, так как ((Int)->())?является синонимами Optional<(Int)->()>, факультативные закрытия убегают.

Янс
источник
5
Теперь получаю@escaping may only be applied to parameters of function type func doStuff(stuff: String, completion: (@escaping ()->())?) {
Lescai Ionel
1
a temporary solution is remove the @escaping attribute from optional function type, that keep the function as escaping. Можете ли вы объяснить это дальше? Семантика по умолчанию в swift 3 не экранирующая. Хотя он компилируется без @escaping, я боюсь, что это вызовет проблемы, будучи обработанным как неэкранирующий. Это не правда?
Пэт Нимейер
49
После дальнейшего прочтения я вижу, что SR-2444 говорит, что все необязательные замыкания рассматриваются как экранирование, что является дополнительной ошибкой :) Я собираюсь предположить, что после исправления компиляция предупредит нас о внесении изменений.
Пэт Нимейер,
Может быть, немного не по теме; но как это работает @autoclosure? Там одна и та же ошибка ...
Gee.E
это работает без @escaping __ func doStuff (материал: строка, завершение: (() -> ())?) {
Феннур Мезитов
226

от: список рассылки swift-users

По сути, @escaping действителен только для замыканий в позиции параметров функции. Правило noescape по умолчанию применяется только к этим замыканиям в позиции параметров функции, в противном случае они экранируются. Агрегаты, такие как перечисления со связанными значениями (например, необязательные), кортежи, структуры и т. Д., Если они имеют замыкания, следуют правилам по умолчанию для замыканий, которые не находятся в положении параметра функции, т.е. они экранируются.

Поэтому необязательным параметром функции по умолчанию является @escaping.
@noeascape применяется только к параметру функции по умолчанию.

Дмитрий Кулеров
источник
7
Я думаю, что это добавляет наиболее важную информацию к предмету, это должен быть принят ответ.
Дамиан Дудич
Мотивированный ответ. Насколько вероятно, что это может измениться?
GoldenJoe
2
Это имеет смысл, поскольку с технической точки зрения высказывание (()->Void)?- это то же самое, что сказать, что у вас есть, Optional<()->Void>и для того, Optionalчтобы сохранить право собственности, он должен был бы принимать только @escapingфункции. Я в третьих, что это должен быть принятый ответ. Спасибо.
Дин Келли
22

Я столкнулся с подобной проблемой, потому что смешивание @escapingи не @escapingочень сбивает с толку, особенно если вам нужно обойти замыкания.

Я закончил тем, что присвоил значение по умолчанию no-op параметру closure через = { _ in }, что, я думаю, имеет больше смысла:

func doStuff(stuff: String = "do stuff",
        completion: @escaping (_ some: String) -> Void = { _ in }) {
     completion(stuff)
}

doStuff(stuff: "bla") {
    stuff in
    print(stuff)
}

doStuff() {
    stuff in
    print(stuff)
}
Свободный Человек
источник
Это чисто и красиво в реализации.
Фред Фауст
2
Что делать, если я хочу проверить, если этот блок ноль / пуст?
Вячеслав Герчиков
2
Облом, это не работает для метода протокола. «Аргумент по умолчанию не разрешен в методе протокола» (Xcode 8.3.2).
Майк Таверн
17

Я получил его в Swift 3 без каких-либо предупреждений только так:

func doStuff(stuff: String, completion: (()->())? ) {
    print(stuff)
    action = completion
    completion?()
}
Игорь
источник
4

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

typealias Action = () -> ()

var action: Action? = { }

func doStuff(stuff: String, completion: Action?) {
    print(stuff)
    action = completion
    completion?()
}

Хорошо, теперь мы позвоним doStuff:

class ViewController: UIViewController {
    var prop = ""
    override func viewDidLoad() {
        super.viewDidLoad()
        doStuff(stuff: "do stuff") {
            print("swift 3!")
            print(prop) // error: Reference to property 'prop' in closure 
                        // requires explicit 'self.' to make capture semantics explicit
        }
    }
}

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

матовый
источник