Contents
Chào bạn đến với Fx Studio,
“
Closures
are self-contained blocks of functionality that can be passed around and used in your code.”— Swift.org —
1. Closure là gì?
Closure là một block code, có thể tách ra để tái sử dụng. Hiểu đơn giản hơn thì Closure là function, nhưng khuyết danh. Ta có thể gán Closure vào biến và sử dụng như các kiểu value khác.
- Các điểm cần chú ý:
- block code
- function
- khuyết danh
- biến
- Ví dụ đơn giản nhất
let simpleClosure = { print("Hello closure") } simpleClosure()
- Giải thích:
- block code được đặt vào giữa 2 dấu
{ }
- simpleClosure là tên biến (bình thường thôi)
- simpleClosure() thì thực thi closure, dấu
( )
dùng để gọi closure thực thi
- block code được đặt vào giữa 2 dấu
- Closure Syntax
2. Phân loại
- Có thể phân được ra 3 loại chính sau:
- Global functions: là closures có tên và không “capture” các giá trị.
- Nested functions: là closures có tên và có thể “capture” các giá trị từ function chứa nó.
- Closure expressions: là closures không có tên được viết dưới dạng giản lược syntax và có thể “capture” các giá trị từ các bối cảnh xung quanh.
Đọc xong đảm bảo không hiểu chi hết.
3. Sử dụng
3.1. Sử dụng trực tiếp
- Ít khi làm loại này
- Viết và chạy trực tiếp
- Từ khoá là
do
3.2. Các closure đã được định nghĩa ra sẵn
- Các function của các class có sẵn
- Sử dụng là chủ yếu
- Hiện tại được nhiều thư viện hay class sử dụng
- Ví du:
- Animate của UIView
-
- Code
UIView.animate(withDuration: 3.0 , animations: { //code here }) { (done) in //completed }
3.3. Tự định nghĩa và sử dụng
- Tham khảo 1 số ví dụ sau:
- Không tham số & Không giá trị trả về
let closrue1 = { () -> Void in // code please! }
-
- Có tham số & không giá trị trả về
let sayHiClosure = { (name: String) in print("Hi, \(name)") } sayHiClosure("Joe")
-
- Có tham số và có giá trị trả về
let addClosure = { (a: Int, b: Int) -> Int in return a + b } print("1 + 1 = \(addClosure(1,1))")
-
- Closure trong 1 closure
let closureInClosure = { (name: String) in simpleClosure() print("I am \(name).") } closureInClosure("Nick")
Suy nghĩ một chút chỗ này.
var myVar: Int var myVar1: () -> () var myVar2: () -> (String) var myVar3: (Int, Int) -> (Int)
- Bạn có tìm ra điểm tương đồng nào không?
- Tất cả đều là khai báo biến
- Tụi nó khác nhau bởi tên (điều hiển nhiên)
- Tụi nó khác nhau kiểu dữ liệu
Vậy để phân biệt các closure với nhau thì ta dựa vào 2 yếu tố được dùng để phân biệt các function. Đó là:
- Parameters : danh sách tham số
- Return type : kiểu giá trị trả về
Chúng ta không thể nào nhớ tụi nó để phân biệt các kiểu function với nhau. Vì vậy tiếp theo là cách định danh để phân biệt tụi nó.
- Tiếp tục qua ví dụ sau:
- Khai báo 1 closure
var add: (Int, Int) -> (Int)
-
- Gán giá trị và thực thi
add = { (a: Int, b: Int) -> (Int) in return a + b } print("1 + 1 = \(add(1,1))")
-
- Gán giá trị khác và tiếp tục thực thi
add = { (a: Int, b: Int) -> (Int) in return a + 2 * b } print("1 + 1 = \(add(1,1))")
- 2 đoạn code cùng 1 kiểu closure nhưng được giá 2 giá trị khác nhau thì sẽ cho kết quả thực thi khác nhau
4. Type alias
- Dùng để định danh một kiểu dữ liệu hiện có.
- Có thể định danh cho các kiểu function.
- Mục đích tạo ra 1 cái tên mới cho kiểu dữ liệu để sử dụng được dễ dàng và tái sử dụng nhiều lần.
- Cú pháp:
typealias <name_alias> = <existing type>
- Ví dụ
//1 typealias AddClosure = (Int, Int) -> Int typealias SubClosure = (Int, Int) -> Int //2 var add: AddClosure = { (a: Int, b: Int) -> (Int) in return a + b } print("1 + 3 = \(add(1,3))") //3 add = { (a: Int, b: Int) -> (Int) in return a + 2 * b } print("1 + 3 = \(add(1,3))") //4 var sub: SubClosure //5 sub = { (a: Int, b: Int) -> (Int) in return a - b } print("5 - 2 = \(sub(5,2))")
- Giải thích:
- 1 : định nghĩa 2 kiểu closure
- 2 : khai báo 1 closure và gán giá trị vào. Sau đó thực thi
- 3 : gán giá trị mới cho closure trên và thực thi
- 4 : khai báo 1 closure khác, nhưng không gán giá trị nào hết
- 5 : gán giá trị cho closure và thực thi
- Xoắn não thêm một chút nữa
var sub1: SubClosure var sub2: SubClosure? var sub3: SubClosure! var result1 = sub1(1,1) var result2 = sub2!(2,3) var result3 = sub3(3,3)
Tiếp theo, bạn cần suy nghĩ về câu hỏi dưới đây một chút nha. Vậy,
“Mục đích to lớn nhất của
closure
là để làm gì?”
5. Mục đích sử dụng closure
“Mục đích quan trọng của sử dụng closure là sử dụng nó như một biến/hằng.”
- Có thể liệt kê như sau:
- Sử dụng chúng như là biến/hằng
- Sử dụng như các tham số trong khai báo 1 function.
- Sử dụng làm đối số để truyền vào các lời gọi hàm.
- Sử dụng như là các property trong class, struct, enum.
- Tái sử dụng hay kế thừa lại.
5.1. Sử dụng trong tham số của function
- Tham khảo đoạn code sau
//1 typealias Completed = (Int, Bool) -> Void //2 func add(a: Int, b: Int, completed: Completed) { let result = a + b completed(result, true) } //3 let myComplete = { (result: Int, done: Bool) in if done { print("Result: \(result)") } else { print("Failed") } } //4 add(a: 10, b: 25, completed: myComplete)
- Giải thích:
- 1 : khai báo 1 kiểu closure
- 2 : khai báo 1 function
- 3 : tạo 1 closure
- 4 : gọi hàm với các đối số (có đối số là closure)
- Hoặc có thể lồng closure vào trực tiếp khi gọi function
add(a: 17, b: 84) { (result, done) in if done { print("Result: \(result)") } else { print("Failed") } }
5.2. Sử dụng như là Delegate & Datasource
- Đây cũng làm mục đích quan trọng nhất trong việc thành tạo sử dụng closure.
- Sau này sẽ được áp dụng vào nhiều kiểu lập trình nâng cao khác như:
- Functional programming
- Reactive programming
- …
- Giúp cho việc code rõ ràng dễ hiểu hơn so với cách dùng protocol.
- Khó khăn thì cần phải có tư duy bất đồng bộ trong lập trình một chút.
- Xem qua ví dụ sau
- Class tính toán
protocol AddOperatorDelegate { func calculate(first: Int, second: Int, result: Int) } protocol AddOperatorDatasource { func getFirstNumber() -> Int func getSecondNumber() -> Int } class MyCalculator { var delegate: AddOperatorDelegate? var dataSource: AddOperatorDatasource? init() { } func add() -> Void { guard let dataSource = dataSource else { print("error") return } let first = dataSource.getFirstNumber() let second = dataSource.getSecondNumber() let result = first + second if let delegate = delegate { delegate.calculate(first: first, second: second, result: result) } } }
-
- Định nghĩa 2 protocol
- Delegate : trả kết quả về class khác
- DataSource : lấy giá trị từ class khác
- Định nghĩa 2 protocol
-
- Class khác (nơi handle kết quả và cung cấp dữ liệu)
class MyClass: AddOperatorDatasource, AddOperatorDelegate { var first: Int var second: Int init(first: Int, second: Int) { self.first = first self.second = second } func handle() { let myCalculator = MyCalculator() myCalculator.delegate = self myCalculator.dataSource = self myCalculator.add() } // Delegate & DataSource Protocol implement func getFirstNumber() -> Int { return first } func getSecondNumber() -> Int { return second } func calculate(first: Int, second: Int, result: Int) { print("\(first) + \(second) = \(result)") } } //Handle var myClass = MyClass(first: 28, second: 15) myClass.handle()
- Class MyClass
- Không trực tiếp tình toán
- Chỉ cung cấp dữ liệu
- Nhận kết quả trả về
Chuyển sang mục đích chính thôi.
- Thêm closure vào class MyCalculator
typealias AddOperatorComplete = (Int, Int, Int) -> Void typealias FirstNumber = () -> Int typealias SecondNumber = () -> Int
- Thêm 1 function với closure làm tham số cho class MyCalculator
func add(first: FirstNumber, second: SecondNumber, completion: AddOperatorComplete) { let first = first() let second = second() let result = first + second completion(first, second, result) }
- Viết lại hàm handle bên class MyClass
- Các đối số là function cho các tham số closure
- Xử lý kết quả trả về từ completion
func handleClosure() { let myCalculator = MyCalculator() myCalculator.add(first: { () -> Int in return first }, second: { () -> Int in return second }) { (first, second, result) in print("\(first) + \(second) = \(result)") } }
- Gọi hàm chạy
//Handle var myClass = MyClass(first: 28, second: 15) myClass.handleClosure()
Đơn giản phải không nào. EZ game!
6. Capture
6.1. Vấn đề
“Điều gì sẽ xảy ra khi sử dụng closure trong trường hợp bất đồng bộ?”
Khi thực thi 1 function với các closure thì nó sẽ trải qua một số bước sau:
- Bước 1: Các closure được định nghĩa
- Sử dụng closure là các đối số của function được gọi
- Các giá trị biến được gắn vào closure
- Bước 2: Thực hiện function
- Giá trị của các tham số trong hàm được truyền vào từ closure
- Hàm được gọi và sau khi kết thúc hàm thì các đối số sẽ được giải phóng
- Các closure truyền vào cũng được giải phóng theo
- Bước 3: Nhận giá trị trả về
- Function return về thì các closure mà đã bị giải phóng rồi nên việc thực hiện tiếp sẽ không thể được
Làm thế nào để các closure đó không bị giải phóng?
5.2. @nonescaping closure
- Với swift 1 và 2 thì mặc địch sẽ là không phải @nonescaping. Nên khi khai báo thì chúng ta cần phải thêm từ khoá @nonescaping vào.
- Với swift 3 và 3 thì ngược lại.
- Life cycle của một @nonescaping closure rất đơn giản:
- Closure được pass vào function argument.
- Function excute closure. Closure được giải phóng khỏi bộ nhớ.
- Function return.
- Khi sử dụng @nonescaping closure, compiler cũng không cần reference tới self, không xảy ra trường hợp reference cycle. Vì vậy không cần sử dụng
[weak self]
hoặc[unowned self]
.
- Ví dụ:
func getSumOf(numbers: [Int], completion: (Int) -> Void) { var sum = 0 for aNumber in numbers { sum += aNumber } completion(sum) } func doSomeThing() { getSumOf(numbers: [24, 5, 29, 18, 97, 43]) { (result) in print("Sum is \(result)") } } doSomeThing()
5.3. @escaping closure
- Với swift 3 và 4 thì phải sử dụng từ khoá @escaping.
- Khi cần giữ lại closure thành property của class để chờ excute sau.
- Để closure có thể được giữ lại trong bộ nhớ sau khi function excute xong và return compiler.
- Ví dụ: chờ response API…
- Để closure có thể được giữ lại trong bộ nhớ sau khi function excute xong và return compiler.
- Khi excute closure trên một thread khác, một asynchronous dispatch queue.
- Queue này sẽ giữ closure trong bộ nhớ, nhằm excute sau. Trường hợp này, chúng ta sẽ không biết được closure sẽ được excute chính xác vào thời điểm nào.
- Ví dụ viết lại ví dụ trên với delay 5 giây mới trả kết quả về
- Nó tương tự như request API thì diễn ra bất đồng bộ
- Thời gian nhận giá trị trả về thì không xác định được
func getSumOf(numbers: [Int], completion: @escaping (Int) -> Void) { var sum = 0 for aNumber in numbers { sum += aNumber } DispatchQueue.main.asyncAfter(deadline: .now() + 5) { print("After 5 secs") completion(sum) } } func doSomeThing() { getSumOf(numbers: [24, 5, 29, 18, 97, 43]) { (result) in print("Sum is \(result)") } } doSomeThing()
6. Các ứng dụng của Closure nên biết
- sorted(): dùng để thay đổi điều kiện sort cho array/collection…
- filter(): dùng dể lọc các phần tử của collection/array với điều kiện nhất định, ví dụ như lọc tuổi người dùng để kiểm tra nhưng ai đủ tuổi xem 18+ chẳng hạn
- map(): dùng để áp dụng điều kiện nào đó cho tất cả các item trong array/collection, ví dụ tính tiền của sản phẩm sau khi áp dụng thuế tiêu thụ chẳng hạn
- reduce(): dùng để tính tổng của array…Xem ví dụ bên dưới, bài toán là cần tính tổng tất cả các sách có trong kho, mỗi record sách được lưu dưới dạng: tên sách, số lượng, và giá.
Hết rồi.
Tổng kết
- Cú pháp closure
- Phân loại các closure
- Cách dùng các loại closure
- Định danh closure
- Sử dụng vào function như tham số
- Sử dụng như Delegate & DataSource
- Capture closure
- Một số closure hay dùng
Related Posts:
Written by chuotfx
Hãy ngồi xuống, uống miếng bánh và ăn miếng trà. Chúng ta cùng nhau đàm đạo về đời, về code nhóe!
1 comment
Leave a Reply Cancel reply
Fan page
Tags
Recent Posts
- Charles Proxy – Phần 1 : Giới thiệu, cài đặt và cấu hình
- Complete Concurrency với Swift 6
- 300 Bài code thiếu nhi bằng Python – Ebook
- Builder Pattern trong 10 phút
- Observer Pattern trong 10 phút
- Memento Pattern trong 10 phút
- Strategy Pattern trong 10 phút
- Automatic Reference Counting (ARC) trong 10 phút
- Autoresizing Masks trong 10 phút
- Regular Expression (Regex) trong Swift
You may also like:
Archives
- September 2024 (1)
- July 2024 (1)
- June 2024 (1)
- May 2024 (4)
- April 2024 (2)
- March 2024 (5)
- January 2024 (4)
- February 2023 (1)
- January 2023 (2)
- November 2022 (2)
- October 2022 (1)
- September 2022 (5)
- August 2022 (6)
- July 2022 (7)
- June 2022 (8)
- May 2022 (5)
- April 2022 (1)
- March 2022 (3)
- February 2022 (5)
- January 2022 (4)
- December 2021 (6)
- November 2021 (8)
- October 2021 (8)
- September 2021 (8)
- August 2021 (8)
- July 2021 (9)
- June 2021 (8)
- May 2021 (7)
- April 2021 (11)
- March 2021 (12)
- February 2021 (3)
- January 2021 (3)
- December 2020 (3)
- November 2020 (9)
- October 2020 (7)
- September 2020 (17)
- August 2020 (1)
- July 2020 (3)
- June 2020 (1)
- May 2020 (2)
- April 2020 (3)
- March 2020 (20)
- February 2020 (5)
- January 2020 (2)
- December 2019 (12)
- November 2019 (12)
- October 2019 (19)
- September 2019 (17)
- August 2019 (10)