Việc sử dụng đóng tham số không thoát có thể cho phép nó thoát


139

Tôi có một giao thức:

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

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

Với một ví dụ thực hiện:

    /// 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)
            }
        }
    }

Đoạn mã trên được biên dịch và hoạt động trong Swift3 (Xcode8-beta5) nhưng không hoạt động với beta 6 nữa. Bạn có thể chỉ cho tôi nguyên nhân cơ bản?


5
Đây là một bài viết rất hay về lý do tại sao nó được thực hiện theo cách đó trong Swift 3
Honey

1
Không có nghĩa là chúng ta phải làm điều này. Không có ngôn ngữ khác yêu cầu nó.
Andrew Koster

Câu trả lời:


243

Điều này là do sự thay đổi trong hành vi mặc định cho các tham số của loại chức năng. Trước Swift 3 (cụ thể là bản dựng đi kèm với Xcode 8 beta 6), chúng sẽ mặc định thoát ra - bạn sẽ phải đánh dấu chúng @noescapeđể ngăn chúng được lưu trữ hoặc bắt giữ, điều này đảm bảo chúng sẽ không tồn tại trong thời gian dài của chức năng gọi.

Tuy nhiên, bây giờ @noescapelà mặc định cho các tham số gõ chức năng. Nếu bạn muốn lưu trữ hoặc nắm bắt các chức năng đó, bây giờ bạn cần đánh dấu chúng @escaping:

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

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

Xem đề xuất Swift Evolution để biết thêm thông tin về thay đổi này.


2
Nhưng, làm thế nào để bạn sử dụng một bao đóng để nó không cho phép thoát ra?
Eneko Alonso

6
@EnekoAlonso Không hoàn toàn chắc chắn những gì bạn đang hỏi - bạn có thể gọi một tham số hàm không thoát trực tiếp trong chính hàm đó hoặc bạn có thể gọi nó khi bị bắt trong một bao đóng không thoát. Trong trường hợp này, khi chúng ta xử lý mã không đồng bộ, không có gì đảm bảo rằng asynctham số hàm (và do đó completionhàm) sẽ được gọi trước khi fetchDatathoát - và do đó phải như vậy @escaping.
Hamish

Cảm thấy xấu xí khi chúng ta phải chỉ định @escaping làm chữ ký phương thức cho các giao thức ... đó có phải là điều chúng ta nên làm không? Đề nghị không nói! : S
Sajjon

1
@Sajjon Hiện tại, bạn cần phải khớp một @escapingtham số trong yêu cầu giao thức với @escapingtham số trong việc thực hiện yêu cầu đó (và ngược lại đối với các tham số không thoát). Swift 2 cũng vậy @noescape.
Hamish


30

Vì @noescape là mặc định, có 2 tùy chọn để sửa lỗi:

1) như @ Hamish đã chỉ ra trong câu trả lời của mình, chỉ cần đánh dấu sự hoàn thành là @escaping nếu bạn quan tâm đến kết quả và thực sự muốn nó thoát (đó có thể là trường hợp trong câu hỏi của @ Lukasz với Bài kiểm tra đơn vị là ví dụ và khả năng không đồng bộ hoàn thành)

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

HOẶC LÀ

2) giữ hành vi @noescape mặc định bằng cách hoàn thành tùy chọn loại bỏ kết quả hoàn toàn trong các trường hợp khi bạn không quan tâm đến kết quả. Ví dụ: khi người dùng đã "bỏ đi" và bộ điều khiển xem cuộc gọi không phải treo trong bộ nhớ chỉ vì có một số cuộc gọi mạng bất cẩn. Giống như trong trường hợp của tôi khi tôi đến đây để tìm câu trả lời và mã mẫu không phù hợp với tôi, vì vậy đánh dấu @noescape không phải là lựa chọn tốt nhất, mặc dù nó có vẻ như là cái duy nhất từ ​​cái nhìn đầu tiên.

func fetchData(location: String, completion: ((DataFetchResult) -> Void)?) {
   ...
   completion?(self.result)
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.