2016-08-17 16 views
80

mam protokołu:zastosowanie zamknięcia braku ucieczki parametru może pozwolić uciec

enum DataFetchResult { 
    case success(data: Data) 
    case failure 
} 

protocol DataServiceType { 
    func fetchData(location: String, completion: (DataFetchResult) -> (Void)) 
    func cachedData(location: String) -> Data? 
} 

Z przykładowej implementacji:

/// An implementation of DataServiceType protocol returning predefined results using arbitrary queue for asynchronyous mechanisms. 
    /// Dedicated to be used in various tests (Unit Tests). 
    class DataMockService: DataServiceType { 

     var result  : DataFetchResult 
     var async  : Bool = true 
     var queue  : DispatchQueue = DispatchQueue.global(qos: .background) 
     var cachedData : Data? = nil 

     init(result : DataFetchResult) { 
      self.result = result 
     } 

     func cachedData(location: String) -> Data? { 
      switch self.result { 
      case .success(let data): 
       return data 
      default: 
       return nil 
      } 
     } 

     func fetchData(location: String, completion: (DataFetchResult) -> (Void)) { 

      // Returning result on arbitrary queue should be tested, 
      // so we can check if client can work with any (even worse) implementation: 

      if async == true { 
       queue.async { [weak self ] in 
        guard let weakSelf = self else { return } 

        // This line produces compiler error: 
        // "Closure use of non-escaping parameter 'completion' may allow it to escape" 
        completion(weakSelf.result) 
       } 
      } else { 
       completion(self.result) 
      } 
     } 
    } 

Kod powyżej zestawiane i pracuje w Swift3 (Xcode8- beta5), ale już nie działa z wersją beta 6. Czy możesz wskazać mi na przyczynę?

+5

Jest to bardzo [świetny artykuł] (https://cocoacasts.com/what-do-escaping-and-noescaping-mean-in-swift-3/) dlaczego to zrobić w ten sposób w Swift 3 – Honey

Odpowiedz

130

Jest to spowodowane zmianą domyślnego zachowania parametrów funkcji. Przed wersją Swift 3 (konkretnie kompilacja dostarczona z beta-beta 8 beta 6) domyślnie uniknęliby - musielibyśmy oznaczyć je jako @noescape, aby zapobiec ich przechowaniu lub przechwyceniu, gwarantując, że nie będą się nazywać po wyjściu funkcji.

Jednak teraz domyślnie jest @noescape - należy teraz oznaczyć parametry funkcji jako @escaping, aby poinformować kompilator, że mogą być przechowywane lub przechwytywane.

protocol DataServiceType { 
    func fetchData(location: String, completion: @escaping (DataFetchResult) -> Void) 
    func cachedData(location: String) -> Data? 
} 

func fetchData(location: String, completion: @escaping (DataFetchResult) -> Void) { 
    // ... 
} 

Zobacz Swift Evolution proposal uzyskać więcej informacji na temat tej zmiany.

+1

Ale w jaki sposób używasz zamknięcia, aby nie mógł uciec? –

+6

@EnekoAlonso Nie jesteś całkowicie pewien, o co pytasz - możesz wywołać parametr funkcji nie-uciekający bezpośrednio w samej funkcji, lub możesz wywołać go, gdy jest przechwytywany w zamknięciu nie-uciekającym. W tym przypadku, ponieważ mamy do czynienia z kodem asynchronicznym, nie ma gwarancji, że parametr funkcji 'async' (a zatem funkcja' complete') zostanie wywołany przed zakończeniem funkcji 'fetchData' - a zatem musi to być' @ escaping' . – Hamish

+0

Czuje się brzydko, że musimy określić @escaping jako sygnaturę metody dla protokołów ... czy to powinniśmy zrobić? Wniosek nie mówi! : S – Sajjon

4

Od @noescape jest domyślnym, tam 2 opcje, aby naprawić ten błąd: tylko zaznaczyć

1) @Hamish wskazał w swojej odpowiedzi na zakończenie jak @escaping jeśli dbam o wyniku i naprawdę chcesz go do ucieczki (to prawdopodobnie jest w przypadku @ pytanie Łukasza z testami urządzenia jako przykład i możliwość zakończenia asynchroniczny)

func fetchData(location: String, completion: @escaping (DataFetchResult) -> Void) 

LUB

2) zachować domyślne zachowanie @noescape poprzez ukończenie opcjonalne odrzucając wyniki altoget jej w przypadkach, gdy nie dbasz o wynik. Na przykład, gdy użytkownik już "odszedł", a kontroler widoku wywoływania nie musi zawieszać się w pamięci tylko z powodu nieostrożnego połączenia sieciowego. Tak jak było w moim przypadku, kiedy przyjechałem tutaj szukając odpowiedzi, a przykładowy kod nie był dla mnie zbyt trafny, więc oznaczenie @noescape nie było najlepszą opcją, chociaż brzmiało ono jako jedyne od pierwszego spojrzenia.

func fetchData(location: String, completion: ((DataFetchResult) -> Void)?) { 
    ... 
    completion?(self.result) 
}