Closure/Block trong iOS

Closure là một functional tuyệt vời mà Swift cung cấp cho lập trình viên, thật ra Closure không phải điều đặc biệt chỉ có trên Swift, các ngôn ngữ khác đều có closure, tuy nhiên được gọi bằng cái tên khác do nhà phát triển ngôn ngữ nghĩ ra mà thôi, ví dụ: closure tương ứng với block trong Objective-C, hoặc là lambda trong Java, delegates trong C# (trong JavaScript cũng gọi là Anonymous Function).

Bài viết dưới đây, mình sẽ giới thiệu về Closure theo cách nhìn nhận của bản thân mình, mình sẽ tập trung vào trả lời các câu hỏi sau cho các bạn dễ hình dung:

  • Closure là cái gì?
  • Tại sao phải dùng Closure? Vai trò của nó là gì?
  • Sử dụng Closure như thế nào?
  1. Closure là gì?

Closure là một tập các đoạn code logic được đặt cùng nhau trong một khối (giống với 1 hàm (function/method)), nhờ vào đó nó có thể được dùng để “pass around” ( cái này mình không dịch, các bạn có thể hiểu nó có thể dược dùng như một variable, hoặc truyền như một paramter, hay có thể là giá trị trả về của một function chẳng hạn).

Nói vậy thì hơi chung chung nhỉ, không sao, cứ nhớ cái quan trọng nhất của nó là “pass around” đi, rồi mình sẽ giải thích mục tiêu của Closure sau.

  1. Tại sao phải dùng Closure? Vai trò của nó là gì?

Nếu như chỉ nói như ở trên thì nó có khác gì mấy với function đâu, cũng là một đống code nhét vào 1 khối, rồi thực hiện mỗi khi nó được gọi, vậy sinh ra cái Closure này làm cái quái gì chứ, tự dưng phải học đau cả đầu.

Không, sự thực không như vậy, hãy sử dụng tư duy trừu tượng để tưởng tượng như sau, bạn có kiểu Int, là mộp tập hợp một integer, kiểu String là tập hợp string, và bạn có thể gán chúng vào một biến, truyền làm parameters,… thì Closure cũng như vậy, nó là một tập hợp logic code, có thể gán vào một biến, truyền làm param mà mình đã nói ở trên. Mặt khác, bên trong Closure là một tập hợp dòng code, chính vì vậy, bạn có thể gọi nó ra để thực hiện như một hàm, và điều khiển luồng theo yêu cầu một cách thuận tiện và dễ dàng. Ví dụ:

Screen Shot 2016-04-08 at 2.51.12 PM.png

Ở trên mình khai báo một Closure, mình đặt tên nó là closure1, bạn khoan nhìn vào cách mình khai báo, hãy nhìn xuống phía dưới: mình có khai báo thêm một biến nữa, đặt tên là variable, và bạn thấy đó, mình hoàn toàn có thể gán variable = closure1. Tiếp đến, mình tiến hành gọi nó như là gọi hàm luôn closure1(), điều này tương ứng với khối lệnh bên trong closure1 được nạp để thực thi, ở đây như bạn thấy là in ra dòng “Hello i’m using closure to say hello with you”. Và bạn thấy dòng chữ đó được in ra ở góc phải kia rồi phải không. Vậy đấy, nó có thể gọi để thực hiện như một function, lại còn có thể gán và tùy biến như một kiểu, một biến luôn chứ, quá tiện phải không :D.

Closure thuận tiện như vậy, nên được sử dụng cực kì rộng rãi, closure thường xuyên được sử dụng để CALL BACK. Đúng vậy, chính vì sự linh hoạt có thể được truyền qua truyền lại, kết hợp với khả năng có thể thực thi lệnh bất cứ khi nào gọi của mình, mà Closure có thể được dùng để call back (gọi lại). Closure hoàn toàn có thể sử dụng các property của class hiện tại, tức là nó hoàn toàn có khả năng gán, thay đổi giá trị, hoặc thực hiện logic nào đó một cách tự nhiên, chính vì vậy khi kết hợp với khả năng call back của mình, closure trở nên cực kì quan trọng. Khả năng call back là như thế nào?. Xem hình sau:Screen Shot 2016-04-08 at 3.08.23 PM.png

Mình khai báo một closure là callBackClosure. Mình có một hàm tên là testFunct(), parameter của nó tên là handler truyền vào là cũng là một closure. Trong hàm testFunct(), mình in ra một dòng, tiếp đến mình tiến hành gọi handler ( nó là closure, nên nó gọi được như là một hàm mà). Cuối cùng, hàm testFunct() sẽ được gọi ở hàm main của mình, mình truyền callBackClosure vào làm parameter cho hàm testFunct(), cuối cùng là build và chạy thôi. Oh yeah, kết quả hiển thị kìa, bạn hiểu nó chứ ?? Ok, Mình giải nghĩa nhé, khi hàm testFunct được gọi thực hiện, nó sẽ thực hiện tuần tự từ trên xuống dưới, và vì mình in dòng “Yeah right…” ra trước, nên nó sẽ được hiển thị trước. Sau khi nó in ra dòng đó rồi thì sao? Nó sẽ gọi thực hiện cái code bên trong parameter handler của nó. Và param đó là cái gì ? Chính là callBackClosure mà mình truyền vào, đồng nghĩa với dòng lệnh bên trong closure đó được thực hiện.

Bạn đã thấy tính call back của nó chưa? Thật ra ví dụ này sẽ dễ hiểu và hình dung hơn, nếu mình đặt nó ở 2 class khác nhau, hay trong iOS là 2 viewController khác nhau.

Bản chất của cái callback được thực hiện chính là nhờ vào khả năng execute code mỗi lần được gọi của closure. Giả sử, bạn có 2 class riêng biệt, class 1 có define closure, class 2 gọi closure đó, và ngay khi class 2 gọi, đoạn code trong closure được thực hiện tức thì, bạn hoàn toàn có thể xử lý, debug khối lệnh của closure này ngay tại thời điểm đó.

Chính nhờ tính callback này mà Closure có thể sử dụng thay thế delegate ( đọc bài delegate để hiểu delegate là cái gì nhé) trong một số trường hợp, thật ra là thay thế ở cái khoản bắt sự kiện mà thôi, còn thay thế cả một delegate pattern (protocol) thì không thể.

  1. Sử dụng Closure thế nào:

Bạn nào học C# mà biết thằng Delegates thì sẽ hình dung Closure dễ dàng hơn, hoặc học JavaScript mà biết closure native của nó thì lại càng tiện. Thôi không ba hoa nữa:

Screen Shot 2016-04-08 at 3.53.01 PM.png

Screen Shot 2016-04-08 at 3.52.06 PM.png

Bonus :)))

Screen Shot 2016-04-06 at 17.35.58.png

Swift được sử dụng rộng rãi trong các thư viện của Swift, vì việc gọi hàm hay sử dụng delegate trong một số trường hợp sẽ khá rối code và rắc rối để follow theo luồng xử lý của nó. Ví dụ khi tương tác với Server API, ở các version trước của iOS sử dụng NSUrlConnection, và theo đó bạn phải implement một đống delegate kèm theo. Thế nhưng ở iOS 8 trở đi, Apple đã thay thế hoàn toàn bằng NSURLSession với thực hiện callback hoàn toàn bằng closure:

Screen Shot 2016-04-08 at 4.02.32 PM.png

Screen Shot 2016-04-08 at 4.04.39 PM.png

Hoặc ví dụ đơn giản nhất về ứng dụng và sử dụng closure trong Swift chính là việc Apple đã vận dụng chúng trong filter và xử lý các Array của mình.

Screen Shot 2016-04-08 at 4.14.42 PM.png

  1. Sử dụng closure để thay thế delegate:

Closure hoàn toàn có thể sử dụng để bắt sự kiện (send message) giữa 2 View Controller nhờ vào khả năng call back đã nói ở trên. Xem xét ví dụ sau:

Screen Shot 2016-04-08 at 20.32.31

Mình có 2 ViewController, đặt tên là ViewController và NextViewController.

-Tại ViewController: mình có 1 nút, khi click vào sẽ push sang NextViewController.

-Tại NextViewController: mình cũng có 1 nút, khi click vào thì ViewController cũng biết sự kiện này, và thực hiện ngay lập tức.

Kịch bản này thông thường sẽ dùng delegate, nhưng hôm nay mình muốn dùng Closure để thử nghiệm. Vậy cách dùng thế nào?

Tại ViewController:

Screen Shot 2016-04-08 at 20.36.13

handleClick là 1 Closure, mình định nghĩa nó giống 1 biến, được đặt tại NextViewController, đồng thời mình viết code thực thi bên trong của nó là dòng “I’m called whenever you click at NextView”.

Tại NextViewController:

Screen Shot 2016-04-08 at 20.36.25

Mình khai báo 1 Closure là handleClick, bản chất là () -> (), tức là nhận vào void và trả về void. Kế đến, mình thực hiện gọi closure này mỗi khi click vào nút.

Và đây là kết quả thực hiện mỗi lần mình click vào cái nút ở NextViewController.

Screen Shot 2016-04-08 at 20.40.24

Mình sẽ giải nghĩa hoạt động của nó, khi bạn click vào nút ở NextViewController, đồng thời bạn sẽ gọi để execute cái closure handleClick, vậy những dòng lệnh để thực thi nó nằm ở đâu? Không phải chính là đoạn print() ở hàm prepareSegue trong ViewController trước hay sao? Như vậy, closure sẽ thực thi đoạn code đó, và vì closure hoàn toàn có thể sử dụng các variable hay property của class ViewController, thế nên bạn có thể custom chúng theo logic mong muốn –> đây chính là call back mà mình đã nói.

Nếu bạn tinh ý, bạn sẽ thấy, cái ví dụ về NSURLConnection và NSURLSession kia, thực chất chính là ví dụ về delegate và closure thay thế delegate, và bạn hãy tư duy mở rộng nó ra với toàn bộ các closure được tích hợp sẵn trong SDK của Apple nhé :D.

Ok, vậy là mình đã xong những khái niệm cơ bản về Closure, hi vọng blog này giúp cho các bạn hiểu rõ hơn về chúng, bạn chỉ cần nhớ 3 vấn đề cốt lõi mình đặt ra ở đầu blog là đủ.