Swift @escaping và Trình xử lý hoàn thành


98

Tôi đang cố gắng hiểu chính xác hơn "Closure" của Swift.

Nhưng @escapingCompletion Handlerquá khó hiểu

Tôi đã tìm kiếm nhiều bài đăng và tài liệu chính thức của Swift, nhưng tôi cảm thấy vẫn chưa đủ.

Đây là ví dụ mã của các tài liệu chính thức

var completionHandlers: [()->Void] = []

func someFunctionWithEscapingClosure(completionHandler: @escaping ()->Void){
    completionHandlers.append(completionHandler)
}

func someFunctionWithNoneescapingClosure(closure: ()->Void){
    closure()
}

class SomeClass{
    var x:Int = 10
    func doSomething(){
        someFunctionWithEscapingClosure {
            self.x = 100
            //not excute yet
        }
        someFunctionWithNoneescapingClosure {
            x = 200
        }
    }
}

let instance = SomeClass()
instance.doSomething()
print(instance.x)

completionHandlers.first?() 
print(instance.x)

Tôi nghe nói rằng có hai cách và lý do sử dụng @escaping

Đầu tiên là để lưu trữ một bao đóng, thứ hai là cho các mục đích hoạt động Async.

Sau đây là những câu hỏi của tôi :

Đầu tiên, nếu doSomethingthực thi thì someFunctionWithEscapingClosuresẽ thực thi với tham số đóng và đóng đó sẽ được lưu trong mảng biến toàn cục.

Tôi nghĩ rằng đóng cửa là {self.x = 100}

Làm cách nào selftrong {self.x = 100} được lưu trong biến toàn cục completionHandlerscó thể kết nối với instanceđối tượng đó của SomeClass?

Thứ hai, tôi hiểu someFunctionWithEscapingClosurenhư thế này.

Để lưu trữ biến cục bộ, đóng từ khóa completionHandlerbiến toàn cục 'completeHandlers we using@ Escape`!

không có @escapingtừ khóa someFunctionWithEscapingClosuretrả về, biến cục bộ completionHandlersẽ xóa khỏi bộ nhớ

@escaping giữ sự khép kín đó trong ký ức

Thê nay đung không?

Cuối cùng, tôi chỉ tự hỏi về sự tồn tại của ngữ pháp này.

Có thể đây là một câu hỏi rất thô sơ.

Nếu chúng ta muốn một số hàm thực thi sau một số hàm cụ thể. Tại sao chúng ta không gọi một số hàm sau một lệnh gọi hàm cụ thể?

Sự khác biệt giữa việc sử dụng mẫu trên và sử dụng hàm gọi lại thoát là gì?

Câu trả lời:


122

Trình xử lý hoàn thành nhanh Trốn thoát & Không chạy trốn:

Như Bob Lee giải thích trong bài đăng trên blog của anh ấy về Trình xử lý hoàn thành bằng Swift với Bob :

Giả sử người dùng đang cập nhật một ứng dụng trong khi sử dụng nó. Bạn chắc chắn muốn thông báo cho người dùng khi nó được thực hiện. Bạn có thể muốn bật lên một hộp có nội dung "Xin chúc mừng, bây giờ, bạn có thể hoàn toàn thích thú!"

Vì vậy, làm thế nào để bạn chạy một khối mã chỉ sau khi tải xuống hoàn tất? Hơn nữa, làm cách nào để bạn tạo hoạt ảnh cho các đối tượng nhất định chỉ sau khi bộ điều khiển chế độ xem đã được chuyển sang tiếp theo? Chà, chúng ta sẽ tìm hiểu cách thiết kế một cái giống như một ông chủ.

Dựa trên danh sách từ vựng mở rộng của tôi, trình xử lý hoàn thành là viết tắt của

Làm mọi thứ khi mọi thứ đã được hoàn thành

Bài đăng của Bob cung cấp sự rõ ràng về trình xử lý hoàn thành (theo quan điểm của nhà phát triển, nó xác định chính xác những gì chúng ta cần hiểu).

@escaping đóng cửa:

Khi người ta chuyển một bao đóng trong các đối số của hàm, hãy sử dụng nó sau khi phần thân của hàm được thực thi và trả về trình biên dịch. Khi hàm kết thúc, phạm vi của bao đóng đã truyền sẽ tồn tại và tồn tại trong bộ nhớ, cho đến khi bao đóng được thực thi.

Có một số cách để thoát khỏi đóng trong hàm chứa:

  • Lưu trữ: Khi bạn cần lưu trữ bao đóng trong biến toàn cục, thuộc tính hoặc bất kỳ lưu trữ nào khác tồn tại trong quá khứ bộ nhớ của hàm gọi sẽ được thực thi và trả lại trình biên dịch.

  • Thực thi không đồng bộ: Khi bạn đang thực thi đóng không đồng bộ trên hàng đợi gửi đi, hàng đợi sẽ giữ đóng trong bộ nhớ cho bạn, có thể được sử dụng trong tương lai. Trong trường hợp này, bạn không biết khi nào thì quá trình đóng sẽ được thực thi.

Khi bạn cố gắng sử dụng lệnh đóng trong các trường hợp này, trình biên dịch Swift sẽ hiển thị lỗi:

ảnh chụp màn hình lỗi

Để biết rõ hơn về chủ đề này, bạn có thể xem bài đăng này trên Phương tiện .

Thêm một điểm nữa, mà mọi nhà phát triển iOS cần hiểu:

  1. Đóng cửa thoát : Một bao đóng thoát là một bao đóng được gọi sau khi hàm mà nó được truyền để trả về. Nói cách khác, nó tồn tại lâu hơn chức năng mà nó đã được truyền cho.
  2. Bao đóng không thoát : Một bao đóng được gọi trong hàm mà nó được truyền vào, tức là trước khi nó trả về.

@shabhakar, điều gì sẽ xảy ra nếu chúng ta lưu trữ một lệnh đóng nhưng không gọi nó sau đó. Hoặc nếu phương thức được gọi hai lần nhưng chúng ta chỉ gọi hàm đóng một lần. Vì chúng tôi biết rằng kết quả là như nhau.
user1101733,

@ user1101733 Tôi nghĩ bạn đang nói về việc thoát khỏi việc đóng, Đóng cửa sẽ không thực thi cho đến khi bạn không gọi. Trong ví dụ trên, nếu gọi phương thức doSomething 2 lần 2 đối tượng finishHandler sẽ thêm vào mảng finishHandlers. Nếu bạn lấy đối tượng đầu tiên từ mảng finishHandlers và gọi nó sẽ thực thi nhưng số lượng mảng finishHandlers sẽ không đổi (2).
Deepak

@Deepak, Có về việc đóng cửa thoát. giả sử chúng ta không sử dụng mảng và sử dụng biến bình thường để lưu trữ tham chiếu bao đóng vì chúng ta sẽ thực hiện lệnh gọi gần đây nhất. Liệu một số bộ nhớ bị chiếm bởi các lần đóng trước đó sẽ không bao giờ gọi?
user1101733

1
@ user1101733 Đóng là kiểu tham chiếu (giống như lớp), khi bạn gán các bao đóng mới cho biến thì thuộc tính / biến sẽ trỏ đến bao đóng mới để ARC sẽ phân bổ bộ nhớ cho các bao đóng trước đó.
Deepak

28

Đây là một nhóm nhỏ các ví dụ mà tôi sử dụng để nhắc nhở bản thân về cách hoạt động của @escaping.

class EscapingExamples: NSObject {

    var closure: (() -> Void)?

    func storageExample(with completion: (() -> Void)) {
        //This will produce a compile-time error because `closure` is outside the scope of this
        //function - it's a class-instance level variable - and so it could be called by any other method at
        //any time, even after this function has completed. We need to tell `completion` that it may remain in memory, i.e. `escape` the scope of this
        //function.
        closure = completion
        //Run some function that may call `closure` at some point, but not necessary for the error to show up.
        //runOperation()
    }

    func asyncExample(with completion: (() -> Void)) {
        //This will produce a compile-time error because the completion closure may be called at any time
        //due to the async nature of the call which precedes/encloses it.  We need to tell `completion` that it should
        //stay in memory, i.e.`escape` the scope of this function.
        DispatchQueue.global().async {
            completion()
        }
    }

    func asyncExample2(with completion: (() -> Void)) {
        //The same as the above method - the compiler sees the `@escaping` nature of the
        //closure required by `runAsyncTask()` and tells us we need to allow our own completion
        //closure to be @escaping too. `runAsyncTask`'s completion block will be retained in memory until
        //it is executed, so our completion closure must explicitly do the same.
        runAsyncTask {
            completion()
        }
    }





    func runAsyncTask(completion: @escaping (() -> Void)) {
        DispatchQueue.global().async {
            completion()
        }
    }

}

2
Mã này không đúng. Nó thiếu @escapingvòng loại.
Rob

Tôi thích điều này nhấti.e. escape the scope of this function.
Gal
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.