Nếu bạn đã từng làm việc với block trong C, Objective-C hay lambdas trong Ruby, khi đó bạn sẽ thấy không khó khăn khi tiếp cận với khái niệm Closures trong Swift.
Closures không khác gì nhiều với block của một hàm(Function) nào đó mà bạn thấy trong code. Để hiểu rõ hơn bạn có thể tham khảo bài hướng dẫn sau.
Nội dung chính
Closure là gì?
Closure là làm một hàm mà không có từ khoá func , có thể có hoặc không có tên đại diện cho closure đó và có thể có từ khóa in hoặc có thể hiểu Closures là một block của một hàm nào đó, nó có thể dùng làm một đối số (argument) hay là một property của object và dùng trong nhiều trường hợp khác nhau mà chúng ta sẽ tìm hiểu trong bài viết này. Tính linh hoạt – Bạn đã biết về hàm nó cũng khá hữu dụng và linh hoạt, vì thế nên closures cũng như vậy, nó rất linh hoạt và trong bài viết này chúng ta sẽ tìm hiểu vấn đề đó. Quản lý bộ nhớ – Closure trong Swift có nhiều lợi ích, đặt biệt trong quản lý bộ nhớ cho bạn, developer sẽ không cần lo lắng nhiều quản lý bộ nhớ, swift sẽ tự quản lý điều đó. Nó sẽ tránh được trường hợp retain cycle, giảm memory leaks hoặc crash ứng dụng khi con trỏ bị nil.
Closures có 3 loại:
- Global functions – Nó giống như khai báo một biến giá trị nào đó, nó không có khả năng sử dụng và thay đổi bất kỳ các biến giá trị nào nằm ngoài khối block của Closues đó.
Ví dụ:
// Closure nó không có đối số và giá trị trả về kiểu String
var hello: () -> (String) = {
return "Hello!"
}
hello() // Hello!
//Closure Có một đối số kiểu Int và kiểu trả về là Int
var double: (Int) -> (Int) = { x in
return 2 * x
}
double(2) // 4
- Nested functions – Nó có tên và được tạo ra trong một hàm nào đó, do đó nó chỉ hoạt động trong phạm vi hàm đó thôi, từ đó nó có khả năng sử dụng và thay đổi bất kỳ biến giá trị nào nằm trong phạm vị hàm mà Closure đang lồng bên trong.
Ví dụ:
func makeIncrementer(forIncrement amount: Int) -> () -> Int {
var runningTotal = 0
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer
}
Ở ví dụ trên biến runningTotal có thể được sử dụng trong Closure incrementer().
- Closures expressions – Nó không có tên và có khả năng nắm giữ hoặc thay đổi giá trị của một biến nào đó mà biến đó được viết dưới dạng Closure.
Ví dụ:
var numbers = [1, 4, 2, 5, 8, 3]
numbers.sort(by: { x, y in
return x < y
})
print(numbers)
// [1, 2, 3, 4, 5, 8]
Cú pháp
Cú pháp để khai báo một Closure có kiểu trả về:
{ (parameters) -> return type in
Statements
}
Cách khai báo một Closure không kiểu trả về:
{ (parameters) in
statements
}
Lưu ý: Closure có thể dùng từ khóa inout cho parameter nhưng không thể gán giá trị mặc định cho parameter.
Một số ví dụ về cách khái báo một Closure:
var noParameterAndNoReturnValue: () -> () = {
print("Hello!")
}
var noParameterAndReturnValue: () -> (Int) = {
return 1000
}
var oneParameterAndReturnValue: (Int) -> (Int) = { x in
return x % 10
}
var multipleParametersAndReturnValue: (String, String) -> (String) = { (first, second) -> String in
return first + " " + second
}
Với các ví dụ bên trên, chúng ta có thể không cần mô tả về các kiểu dữ liệu của các biến Closure vì Swift có khả năng tự suy luận kiểu cho các biến đó. Có thể viết lại như sau:
var noParameterAndNoReturnValue = {
print("Hello!")
}
var noParameterAndReturnValue = { () -> Int in
return 1000
}
var oneParameterAndReturnValue = { (x: Int) -> Int in
return x % 10
}
var multipleParametersAndReturnValue = { (first: String, second: String) -> String in
return first + " " + second
}
Các tính năng của Closure
- Viết tắt tên tham số(Shorthand parameter name)
Swift cung cấp viết tắt tên của các tham số trong thân của Closure, Chúng ta có thể dùng các đối số viết tắc như $0, $1,$2, v.v..
Ví du:
var numbers = [1, 4, 2, 5, 8, 3]
numbers.sort({ return $0 < $1 })
var double: (Int) -> (Int) = {
return $0 * 2
}
// double(4) // 8 // có thể hiểu là $0 bằng 4.
var sum: (Int, Int) -> (Int) = {
return $1 + $2
}
sum(1,2) // 3 // Có thể hiểu $1 = 1, $2 = 2
- Sử dụng và thay đổi các biến giá trị được khái báo bên ngoài Closure nhưng được sử dụng bên trong Closure.(Capturing value)
Bạn có thể xem ví dụ để hiểu hơn về Capturing value:
func changeCase(uppercase: Bool, ofStrings strings: String...) -> [String] {
var newStrings = [String]()
func changeToUppercase() {
for s in strings {
newStrings.append(s.uppercased())
}
}
func changeToLowerCase() {
for s in strings {
newStrings.append(s.lowercased())
}
}
if uppercase {
changeToUppercase()
} else {
changeToLowerCase()
}
return newStrings
}
let uppercasedStates = changeCase(uppercase: true, ofStrings: "California", "New York")
let lowercasedStates = changeCase(uppercase: false, ofStrings: "California", "New York")
Lưu ý: Chỉ có 2 loại nested function và closure expressions mới có tính năng capturing value.
- Trailing Closure Syntax
Nếu hàm của bạn có một đối số là một closure nằm ở cuối hàm, thì khi gọi hàm đó chúng ta sử dụng cú pháp Trailing Closure.
Ví dụ:
var numbers = [1, 4, 2, 5, 8, 3, 5, 6, 7, 8, 7, 9]
numbers.sort { $0 < $1 }
func sum(from: Int, to: Int, f: (Int) -> (Int)) -> Int {
var sum = 0
for i in from...to {
sum += f(i)
}
return sum
}
sum(from: 1, to: 10) {
$0
} // Tính tổng của 10 phần từ đầu tiên
sum(from: 1, to: 10) {
$0 * $0
} // Tính tổng diện tích hình vuông của 10 phần tử đâu tiên
- Closure là một reference types(Một kiểu dữ liệu tham chiếu)
Có nghĩa là khi chúng ta gán một closure này sang một biến khác, thì biến khác đó sẽ có khả năng xử lý của closure bị gán.
Ví dụ:
// Một closure có đối số kiểu Int và kiểu trả về là Int
var double: (Int) -> (Int) = { x in
return 2 * x
}
double(2) // 4
// Gán closure cho một biến khác và xữ lý của biến alsoDouble cũng tương tự double.
var alsoDouble = double
alsoDouble(3) // 6
Closure là một trong những thế mạnh và nội dung quan trong của Swift. Chúng ta có thể sử dụng nó một cách linh hoạt, nhanh đồng thời developer dễ viết, đọc và dễ hiểu code xử lý như thế nào.