2016-09-11 46 views
14

Zauważyłem, że w Swift 2.2 zamknięcia oznaczone jako nie-uciekające z @noescape nie wymagają wyraźnego self. W Swift 3 wszystkie zamknięcia domyślnie nie są zamykane i teraz wymagają oznaczenia jako @escaping, jeśli chcesz, aby były w stanie uciec.Dlaczego zamknięcie wymaga jawnego `self`, gdy wszystkie są domyślnie unikane w Swift 3?

Biorąc pod uwagę, że wszystkie zamknięcia w Swift 3 domyślnie są nie-ucieczkowe, dlaczego wymagają wyraźnej self?

final class SomeViewController: NSViewController { 

    var someClosure:() ->() = { _ in } 

    override func viewDidLoad() { 
     super.viewDidLoad() 

     someClosure = { 
      view.layer = CALayer() // ERROR: Implicit use of `self` in closure; use `self.` to make capture semantics explicit 
     } 
    } 
} 

Odpowiedz

9

w Swift 3, wszystkie zamknięcia są dla ucieczki domyślnie

Nie, w Swift 3, tylko zamknięcie argumenty funkcji (tj wejścia funkcyjne, które są funkcjami siebie) są dla ucieczki przez domyślny (jak na SE-0103). Np

class A { 

    let n = 5 
    var bar :() -> Void = {} 

    func foo(_ closure:() -> Void) { 
     bar = closure // As closure is non-escaping, it is illegal to store it. 
    } 

    func baz() { 
     foo { 
      // no explict 'self.' required in order to capture n, 
      // as foo's closure argument is non-escaping, 
      // therefore n is guaranteed to only be captured for the lifetime of foo(_:) 
      print(n) 
     } 
    } 
} 

closure jak w powyższym przykładzie jest nie ucieka, jest zakazane przechowywane chwytać, ograniczając w ten sposób jego żywotność do życia funkcją foo(_:). Oznacza to, że wszelkie przechwycone wartości nie zostaną przechwycone po wyjściu funkcji - co oznacza, że ​​nie musisz martwić się o problemy, które mogą wystąpić podczas przechwytywania, takie jak zatrzymywanie cykli.

Jednakże zamknięcie przechowywać Obiekt (np bar w powyższym przykładzie), stanowi ucieczki (to jest nonsensowne oznaczyć je @noescape) jako życia nie ograniczony do danej funkcji - to (a zatem wszystkie przechwycone zmienne) pozostaną w pamięci tak długo, jak długo dana instancja pozostanie w pamięci. Może to łatwo prowadzić do problemów, takich jak zatrzymywanie cykli, dlatego konieczne jest użycie jawnego self., aby semantyka przechwytywania była jednoznaczna.

W rzeczywistości, w przypadku punktu, twój przykład kodu tworzy cykl zatrzymania momencie viewDidLoad() miano, jak someClosure mocno chwyta self i self silnie odwołuje someClosure, jak jest to zapisane nieruchomość.


Oczywiście, jedynym miejscem, gdzie można oczekiwać, aby móc użyć atrybutu @noescape jest na zamknięcie, które jest zmienna lokalna w funkcji. Takie zamknięcie miałoby przewidywalną trwałość, o ile nie jest przechowywane poza funkcją lub nie jest przechwytywane. Na przykład:

class A { 

    let n = 5 

    func foo() { 

     let f : @noescape() -> Void = { 
      print(n) 
     } 

     f() 
    } 
} 

Niestety, jak @noescape jest usuwany w Swift 3, to nie będzie to możliwe (Co ciekawe jest to, że w Xcode 8 GM, to jest możliwe, ale daje ostrzeżenie amortyzację). Jako Jon Shier says będziemy musieli poczekać, aż zostanie on ponownie dodany do języka, co może, ale nie musi się zdarzyć.

+0

Dziękujemy za wyjaśnienia! Aby wyjaśnić, kiedy sam kontroler widoku zostanie zwolniony, czy zamknięcie i widok również zostaną zwolnione, czy też spowodują wyciek pamięci? – beingadrian

+0

@beingadrian Jeśli twoja własność 'someClosure' ma przypisane zamknięcie, które silnie przechwytuje' self' (na przykład w twoim przykładzie w 'viewDidLoad'), to faktycznie stworzy cykl zatrzymania, a zatem będzie przeciekał, jeśli nie ma innych silnych odniesienia do kontrolera widoku (możesz to zweryfikować, implementując 'deinit' z instrukcją' print' w klasie kontrolera widoku). Najprostszym rozwiązaniem byłoby przechwycenie 'self' jako' słabego' (zdefiniuj zamknięcie jako '{[słabe self] w ...}'). Następnie możesz użyć opcjonalnego łańcucha w zamknięciu, aby przypisać warstwę widoku, 'self? .view.layer = CALayer()' – Hamish

+0

@beingadrian Innym potencjalnym rozwiązaniem jest przechwycenie 'self' w zamknięciu jako' unowned', jednak jest to potencjalnie niebezpieczne, ponieważ ulegnie awarii, jeśli zamknięcie zostanie wywołane po zwolnieniu instancji kontrolera widoku. Dlatego radziłbym robić to tylko wtedy, gdy 'someClosure' byłaby własnością' private' (wtedy nie ma szansy, aby inna klasa uzyskała odniesienie do niej). – Hamish

4

Przechowywane zamknięcia są uważane za domyślne, nawet jeśli tak naprawdę nie są. Nie można ich oznaczyć jako nie-uciekających, więc utknęliśmy w ten sposób, dopóki nie dodali @noescape z powrotem do języka, który mogą, ale nie robią. Zobacz this discussion na liście mailingowej swift-evolution.

+0

To intrygujące. Mam nadzieję, że przynajmniej poprawią to w aktualizacji.Dziękuję za odpowiedź! – beingadrian