Escaping và Non-Escaping Closures là hai khái niệm quan trọng trong ngôn ngữ lập trình Swift. Closures là một khái niệm mạnh mẽ trong Swift, cho phép bạn gom nhóm mã và truyền nó như một đối tượng.
Escaping Closures và Non-Escaping Closures đều liên quan đến việc quản lý thời gian sống (lifetime) của closures. Thời gian sống của một closure xác định thời điểm mà nó có thể được gọi và tồn tại trong bộ nhớ.
Xem thêm Swift Closures
Một Escaping Closure là một closure được truyền vào một hàm như một tham số, nhưng nó được lưu trữ và có thể được gọi sau khi hàm đã trả về. Điều này có nghĩa là escaping closure có thể “thoát ra” khỏi phạm vi của hàm gọi và được lưu trữ để được sử dụng sau này. Điều này thường được sử dụng khi closure cần được thực thi bất đồng bộ hoặc khi nó cần được truyền qua các hàm hoặc các biến khác.
Trong khi đó, Non-Escaping Closure là một closure không thoát ra khỏi phạm vi của hàm gọi và phải được gọi trước khi hàm kết thúc. Nó được đảm bảo không tồn tại sau khi hàm gọi kết thúc.
Việc phân biệt giữa Escaping và Non-Escaping Closures có ý nghĩa quan trọng trong việc xác định cách quản lý bộ nhớ và sự thực thi của closures. Quản lý đúng thời gian sống của closures là cần thiết để tránh các vấn đề như retain cycle và memory leaks.
Trong Swift, việc xác định một closure có là Escaping hay Non-Escaping được thực hiện bằng cách sử dụng từ khóa @escaping hoặc không sử dụng từ khóa này.
Việc hiểu và sử dụng đúng Escaping và Non-Escaping Closures là quan trọng để viết code an toàn và hiệu quả. Trong bài viết này, chúng ta sẽ tìm hiểu cú pháp, sự khác biệt và cách sử dụng Escaping và Non-Escaping Closures trong Swift.
Khái niệm về Closures trong Swift
Closures là một khái niệm quan trọng trong ngôn ngữ lập trình Swift. Chúng là một cách để gom nhóm và truyền một đoạn mã nhất định như một đối tượng. Closures có thể được xem như một hàm vô danh hoặc một khối mã có thể được gán cho một biến hoặc truyền như một tham số cho một hàm khác.
Closures trong Swift có các đặc điểm sau:
- Closures có thể được gán cho một biến hoặc hằng số: Bạn có thể tạo một closure và gán nó cho một biến hoặc hằng số giống như bạn làm với một giá trị khác.
- Closures có thể được truyền như tham số: Bạn có thể truyền một closure như một tham số cho một hàm khác. Điều này cho phép bạn thực hiện các hành động cụ thể trong một hàm dựa trên một closure được cung cấp từ bên ngoài.
- Closures có thể được trả về từ một hàm: Bạn có thể có một hàm trong Swift trả về một closure, cho phép bạn tạo và trả về các hàm vô danh dễ dàng.
Closures trong Swift có thể được viết dưới các dạng sau:
{ (parameters) -> ReturnType in // Code của closure } // Hoặc nếu closure không có tham số và không có giá trị trả về { // Code của closure }
Trong các closure, bạn có thể sử dụng các biến và hằng số bên ngoài của closure trong phạm vi nơi closure được khai báo. Điều này được gọi là “capturing values” và cho phép closure tham chiếu và sử dụng các giá trị từ môi trường xung quanh.
Closures rất mạnh mẽ và linh hoạt trong việc xử lý các tác vụ như sắp xếp, lọc và xử lý dữ liệu. Chúng cũng giúp giảm thiểu lượng code và tăng tính sáng tạo trong việc viết ứng dụng Swift.
Xem thêm Closure trong javascript
Escaping Closures
Escaping closures là một loại closures trong Swift, được sử dụng khi closure được truyền vào một hàm như một tham số, nhưng nó được lưu trữ và có thể được gọi sau khi hàm đã trả về. Điều này có nghĩa là escaping closure có thể “thoát ra” khỏi phạm vi của hàm gọi và được lưu trữ để được sử dụng sau này.
Cú pháp định nghĩa một escaping closure trong Swift như sau:
func someFunction(completion: @escaping () -> Void) { // Thực thi một số công việc khác // và sau đó gọi closure sau khi hoàn thành DispatchQueue.main.async { completion() } }
Trong ví dụ trên, closure completion
được đánh dấu bằng từ khóa @escaping
để cho biết rằng nó là một escaping closure. Closure này được lưu trữ và sau đó được gọi bên trong một DispatchQueue.main.async
để đảm bảo rằng nó được gọi trong một ngữ cảnh khác sau khi hàm someFunction
đã trả về.
Escaping closures thường được sử dụng trong các tình huống sau:
- Xử lý bất đồng bộ: Khi bạn cần gọi một closure sau khi một tác vụ bất đồng bộ hoàn thành, ví dụ như gọi một closure sau khi một yêu cầu mạng hoàn thành.
- Ghi lại trạng thái: Khi bạn cần ghi lại trạng thái của một đối tượng hoặc thực hiện một hành động sau khi một sự kiện xảy ra, ví dụ như ghi lại trạng thái sau khi người dùng đăng nhập.
- Callbacks: Khi bạn muốn thông báo cho một đối tượng khác khi một sự kiện nào đó xảy ra, ví dụ như gọi một closure khi xử lý hoàn tất.
Tuy nhiên, khi sử dụng escaping closures, chúng ta cần cẩn thận để tránh các vấn đề như retain cycle, khi closure sử dụng một biến mạnh và closure được lưu trữ bởi đối tượng mà nó truy cập. Điều này có thể gây rò rỉ bộ nhớ và gây hiện tượng “strong reference cycle”.
Vì vậy, khi sử dụng escaping closures, chúng ta cần chắc chắn rằng chúng ta sử dụng [weak self]
hoặc [unowned self]
để tránh các vấn đề về bộ nhớ.
Xem thêm Các Funtion trong Swift
Non-Escaping Closures
Non-Escaping Closures là một loại closures trong Swift, nghĩa là closure không thoát ra khỏi phạm vi của hàm gọi và phải được gọi trước khi hàm kết thúc. Điều này có nghĩa là non-escaping closure phải được thực thi trong phạm vi của hàm chứa nó và không được lưu trữ để sử dụng sau này.
Cú pháp định nghĩa một non-escaping closure trong Swift tương tự như định nghĩa bình thường của closure:
func someFunction(completion: () -> Void) { // Thực thi một số công việc khác // và sau đó gọi closure completion() }
Trong ví dụ trên, closure completion
được khai báo như một non-escaping closure mặc định. Nó chỉ có thể được gọi bên trong phạm vi của hàm someFunction
và phải được gọi trước khi hàm kết thúc.
Non-escaping closures thường được sử dụng trong các tình huống sau:
- Callbacks đồng bộ: Khi bạn cần gọi một closure để truyền kết quả về một tác vụ đồng bộ, ví dụ như xử lý kết quả của một phép tính hoặc một tác vụ xử lý dữ liệu.
- Xử lý đồng bộ trong hàm gọi: Khi bạn muốn xử lý một tác vụ trong cùng phạm vi của hàm gọi và không cần lưu trữ closure để sử dụng sau này.
Với non-escaping closures, không cần phải lo lắng về các vấn đề như retain cycle vì closure không được lưu trữ trong bộ nhớ. Tuy nhiên, chúng ta cần đảm bảo rằng closure được gọi trước khi hàm kết thúc và không được truy cập sau khi hàm đã trả về.
Sử dụng non-escaping closures có thể làm mã của chúng ta dễ đọc và dễ hiểu hơn trong một số tình huống đơn giản, vì chúng không yêu cầu quản lý thời gian sống của closures trong môi trường bên ngoài.
Xem thêm Giao thức Mạng trong TCP/IP
Sự khác biệt giữa Escaping và Non-Escaping Closures
Escaping Closures và Non-Escaping Closures là hai loại closures trong Swift với các đặc điểm và cách sử dụng khác nhau. Dưới đây là sự khác biệt giữa chúng:
- Đặc điểm chung:
- Cả Escaping Closures và Non-Escaping Closures đều là closures, tức là một khối mã có thể được gán và truyền như một đối tượng.
- Cả hai có thể được khai báo và sử dụng trong các hàm hoặc phương thức của Swift.
- Escaping Closures:
- Escaping closures là closures có thể “thoát ra” khỏi phạm vi của hàm gọi và được lưu trữ để sử dụng sau này.
- Escaping closures thường được truyền làm tham số cho một hàm và có thể được gọi sau khi hàm đã trả về.
- Để đánh dấu một closure là escaping closure, chúng ta sử dụng từ khóa
@escaping
trước kiểu closure.
- Non-Escaping Closures:
- Non-Escaping closures là closures không thoát ra khỏi phạm vi của hàm gọi và phải được gọi trước khi hàm kết thúc.
- Non-Escaping closures thường được sử dụng trong cùng phạm vi của hàm gọi và không được lưu trữ để sử dụng sau này.
- Non-Escaping closures không cần đánh dấu bằng từ khóa
@escaping
, mà chúng là mặc định khi khai báo closures trong các tham số hàm.
- Sử dụng và ứng dụng:
- Escaping closures thường được sử dụng trong các tình huống bất đồng bộ, xử lý callbacks hoặc khi cần lưu trữ closures để gọi sau khi một tác vụ hoàn thành.
- Non-Escaping closures thường được sử dụng trong các tình huống đồng bộ, xử lý trực tiếp trong phạm vi hàm gọi hoặc khi không cần lưu trữ closures sau khi hàm kết thúc.
Tóm lại, sự khác biệt giữa Escaping Closures và Non-Escaping Closures nằm ở khả năng thoát ra khỏi phạm vi và việc lưu trữ để sử dụng sau này. Việc lựa chọn giữa hai loại closures này phụ thuộc vào các yêu cầu và tình huống cụ thể trong mã của bạn.
Xem thêm Swift 4.0 là gì?
Ví dụ về việc sử dụng Escaping và Non-Escaping Closures
Dưới đây là ví dụ minh họa về việc sử dụng Escaping và Non-Escaping Closures trong Swift:
Ví dụ về Escaping Closures:
// Khai báo một hàm có một escaping closure như tham số func performAsyncTask(completion: @escaping () -> Void) { DispatchQueue.global().async { // Thực hiện một công việc bất đồng bộ // ... // Gọi closure sau khi công việc hoàn thành completion() } } // Gọi hàm và truyền một escaping closure performAsyncTask { print("Công việc bất đồng bộ đã hoàn thành!") }
Ví dụ về Non-Escaping Closures:
// Khai báo một hàm có một non-escaping closure như tham số func performSyncTask(completion: () -> Void) { // Thực hiện công việc đồng bộ // ... // Gọi closure khi công việc hoàn thành completion() } // Gọi hàm và truyền một non-escaping closure performSyncTask { print("Công việc đồng bộ đã hoàn thành!") }
Trong ví dụ trên, performAsyncTask
là một hàm có một escaping closure như tham số. Nó thực hiện một công việc bất đồng bộ và gọi closure sau khi công việc hoàn thành. Trong khi đó, performSyncTask
là một hàm có một non-escaping closure như tham số. Nó thực hiện một công việc đồng bộ và gọi closure khi công việc hoàn thành.
Khi gọi hàm performAsyncTask
, chúng ta truyền một escaping closure để được gọi sau khi công việc bất đồng bộ hoàn thành. Trong khi đó, khi gọi hàm performSyncTask
, chúng ta truyền một non-escaping closure để được gọi ngay trong hàm.
Qua ví dụ trên, bạn có thể thấy cách sử dụng Escaping và Non-Escaping Closures trong các tình huống khác nhau. Tùy thuộc vào yêu cầu của bạn, bạn có thể chọn sử dụng escaping closure hoặc non-escaping closure tương ứng.
Hạn chế và lưu ý khi sử dụng Escaping và Non-Escaping Closures
Khi sử dụng Escaping và Non-Escaping Closures trong Swift, có một số hạn chế và lưu ý cần lưu ý:
Hạn chế khi sử dụng Escaping Closures:
- Khối lượng bộ nhớ: Vì escaping closures được lưu trữ và có thể tồn tại lâu hơn, chúng có thể tiêu tốn nhiều bộ nhớ hơn so với non-escaping closures. Điều này có thể ảnh hưởng đến hiệu suất và tiêu thụ bộ nhớ của ứng dụng.
- Quản lý thời gian sống: Khi sử dụng escaping closures, bạn phải đảm bảo rằng các tham chiếu yếu (weak references) được sử dụng để tránh việc tạo ra retain cycle (vòng lặp chưa được giải phóng bộ nhớ).
- Xử lý bất đồng bộ: Khi sử dụng escaping closures trong môi trường bất đồng bộ, bạn phải đảm bảo rằng các tác vụ hoàn thành và gọi closures chỉ xảy ra khi cần thiết. Điều này có thể yêu cầu xử lý đồng bộ và đồng bộ hoá các tác vụ.
Lưu ý khi sử dụng Non-Escaping Closures:
- Quan sát việc sử dụng: Non-escaping closures phải được gọi trước khi hàm kết thúc. Vì vậy, bạn cần đảm bảo rằng closure được gọi đúng thời điểm và không gây ra lỗi logic.
- Mô hình async: Trong một số tình huống, như xử lý bất đồng bộ hoặc callback, non-escaping closures có thể không phù hợp. Nếu bạn cần chờ đợi kết quả từ một tác vụ bất đồng bộ hoặc truyền kết quả qua các hàm gọi tiếp theo, escaping closures có thể là lựa chọn tốt hơn.
- Xử lý dữ liệu: Nếu bạn cần truy cập và xử lý dữ liệu trong non-escaping closures, bạn cần đảm bảo rằng dữ liệu vẫn tồn tại và hợp lệ tại thời điểm gọi closure.
Như vậy, khi sử dụng Escaping và Non-Escaping Closures, bạn cần xem xét các hạn chế và lưu ý để đảm bảo rằng mã của bạn hoạt động đúng và hiệu quả. Tuỳ thuộc vào tình huống và yêu cầu cụ thể, bạn có thể chọn sử dụng escaping closures hoặc non-escaping closures phù hợp.