Swift opcjonalny uciekający parametr zamknięcia

162

Dany:

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()

Czy istnieje sposób, aby completionparametr (i action) był typem, Action?a także zachować @escaping?

Zmiana typu powoduje następujący błąd:

Atrybut @escaping dotyczy tylko typów funkcji

Po usunięciu @escapingatrybutu kod kompiluje się i uruchamia, ale nie wydaje się być poprawny, ponieważ completionzamknięcie wymyka zakres funkcji.

Lescai Ionel
źródło
21
„Usunięcie @escapingatrybutu powoduje kompilację i uruchomienie kodu” - Dzieje się tak, ponieważ, jak opisano w SR-2444 , Action?domyślnie stosuje się znaki ucieczki. Tak więc usunięcie @escapingpodczas korzystania z opcjonalnego zamknięcia spełnia Twoje potrzeby.
Rob
zamknięcia aliasów typu uciekają
Masih
Oto doskonały artykuł autorstwa Ole Begemanna, który opisuje, dlaczego tak się dzieje i niektóre obejścia, jeśli chcesz, aby opcjonalnymi parametrami były @noescape.
Rozsądny

Odpowiedzi:

122

Istnieje raport SR-2552, który @escapingnie rozpoznaje aliasu typu funkcji. dlatego błąd @escaping attribute only applies to function types. możesz obejść ten problem, rozszerzając typ funkcji w sygnaturze funkcji:

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()

EDYCJA 1 ::

Właściwie byłem pod wersją beta xcode 8, w której błąd SR-2552 nie został jeszcze rozwiązany. naprawiając ten błąd, wprowadziłem nowy (ten, przed którym stoisz), który jest nadal otwarty. patrz SR-2444 .

Obejście, które @Michael Ilseman wskazał jako rozwiązanie tymczasowe, polega na usunięciu @escapingatrybutu z opcjonalnego typu funkcji, który utrzymuje funkcję jako ucieczkę .

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

EDYCJA 2 ::

SR-2444 został zamknięty stwierdzając wyraźnie, że w parametrach pozycji zamknięcia nie ucieka i trzeba je było zaznaczone @escaping, aby je ucieczce, ale opcjonalne parametry niejawnie ucieczki, ponieważ ((Int)->())?jest to synonimy Optional<(Int)->()>, opcjonalne zamknięcia są ucieczki.

Jans
źródło
5
Teraz się@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. Czy możesz to dalej wyjaśnić? Domyślna semantyczna w swift 3 nie jest ucieczką. Chociaż kompiluje się bez @escaping, obawiam się, że spowoduje problemy, ponieważ będzie traktowany jako nie-uciekający. Czy to nieprawda?
Pat Niemeyer
49
Po dalszym czytaniu widzę, że SR-2444 mówi, że wszystkie opcjonalne domknięcia są traktowane jako ucieczki, co jest błędem uzupełniającym :) Zakładam, że kiedy zostanie naprawiony, kompilacja ostrzeże nas o wprowadzeniu zmiany.
Pat Niemeyer
Może trochę poza tematem; ale jak to działa @autoclosure?
Pojawia
działa bez @escaping __ func doStuff (stuff: String, complete: (() -> ())?) {
Феннур Мезитов
226

from: lista mailingowa swift-users

Zasadniczo @escaping jest poprawne tylko w przypadku domknięć w pozycji parametru funkcji. Reguła domyślna noescape ma zastosowanie tylko do tych domknięć na pozycji parametru funkcji, w przeciwnym razie są one znakami ucieczki. Agregaty, takie jak wyliczenia ze skojarzonymi wartościami (np. Opcjonalne), krotki, struktury itp., Jeśli mają zamknięcia, postępują zgodnie z domyślnymi regułami dla domknięć, które nie znajdują się na pozycji parametru funkcji, tj. Są znakami ucieczki.

Tak więc opcjonalnym parametrem funkcji jest domyślnie @escaping.
@noeascape domyślnie stosuje się tylko do parametru funkcji.

Dmitry Coolerov
źródło
7
Myślę, że to dodaje do tematu najważniejszych informacji, należy przyjąć odpowiedź.
Damian Dudycz
Uzasadniona odpowiedź. Jak prawdopodobne jest, że to się zmieni?
GoldenJoe
2
Ma to sens, ponieważ z technicznego punktu widzenia mówienie (()->Void)?jest tym samym, co mówienie, że masz, Optional<()->Void>a Optionalaby zachować własność, musiałby akceptować tylko @escapingfunkcje. Po trzecie, powinna to być akceptowana odpowiedź. Dziękuję Ci.
Dean Kelly
22

Natknąłem się na podobny problem, ponieważ miksowanie @escapingi non- @escapingjest bardzo mylące, szczególnie jeśli trzeba omijać zamknięcia.

Skończyło się na przypisaniu domyślnej wartości no-op do parametru zamknięcia za pośrednictwem = { _ in }, co moim zdaniem ma większy sens:

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)
}
Freeman Man
źródło
To jest czyste i ładne w realizacji.
Fred Faust
2
Co jeśli chcę sprawdzić, czy ten blok jest zerowy / pusty?
Vyachaslav Gerchicov
2
Szkoda, to nie działa w przypadku metody protokołu. „Domyślny argument nie jest dozwolony w metodzie protokołu” (Xcode 8.3.2).
Mike Taverne
17

Mam to działające w Swift 3 bez żadnych ostrzeżeń tylko w ten sposób:

func doStuff(stuff: String, completion: (()->())? ) {
    print(stuff)
    action = completion
    completion?()
}
Igor
źródło
4

Ważne jest, aby zrozumieć na przykład to, że jeśli zmieni Actionsię do Action?zamknięcia się ucieczką. Zróbmy więc to, co proponujesz:

typealias Action = () -> ()

var action: Action? = { }

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

OK, teraz zadzwonimy 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
        }
    }
}

Cóż, ten wymóg pojawia się tylko w przypadku unikania zamknięć. Więc zamknięcie ucieka. Dlatego nie oznaczasz, że ucieka - już ucieka.

matowe
źródło