Closure trong Swift — Không phải chỉ if, for, while mới được mở block

Closure hay còn được biết đến với những cái tên như anonymous function, lambda expression,…

Closure thực ra chính là function mà các bạn được học ở trường lớp, tuy nhiên nó còn hơn cả thế nữa (sẽ nói thêm ở dưới)

Closure trong Swift

Trong Swift, function thực ra cũng là một object với kiểu là:

(tham số) -> kiểu trả về

func sum(a: Int, b: Int) -> Int {
	return a + b
}

Function trên có kiểu là (Int, Int) -> Int

Vì là một object với kiểu riêng nên function có thể được dùng làm tham số hay kết quả trả về của một function khác

func square(a: Int) -> Int {
	return a * a
}

func apply(modifier: (Int) -> Int, array: [Int]) {
	for a in array {
		a = modifier(a)
	}
}

var a = [1, 2, 3, 4]

apply(square, a)

// a == [1,4,9,16]

Thủ tục apply ở trên có tác dụng sử dụng một function nào đó (bắt buộc input 1 số nguyên và output ra 1 số nguyên) lên tất cả các phần tử của một mảng Int

Một ví dụ khác là method Array.sorted(by:) trong thư viện chuẩn của Swift, có tác dụng sắp xếp mảng dựa theo hàm so sánh mà ta cho vào

let things = ["may giat", "tv", "may lanh", "tu lanh"]

func backward(s1: String, s2: String) -> Bool {
	return s1 > s2
}

var reversedThings = things.sorted(by: backward)

// things == ["tv", "tu lanh", "may lanh", "may giat"]

Có những hàm chỉ dùng một lần, ta có thể viết thẳng hàm đó ngay tại chỗ mà không cần khai báo, đây chính là anonymous function (hàm ẩn danh)

var reversedThings = things.sorted(by: { (s1: String, s2: String) -> Bool in

return s1 > s2})

Cú pháp của hàm ẩn danh là:

{ (tham số) -> kiểu trả về in

các câu lệnh }

Với các closure mà ta có thể nội suy được kiểu tham số và kiểu trả về (ví dụ như khi được pass vào 1 function khác) thì có thể bỏ tên kiểu đi

var reversedThings = things.sorted(by: { s1, s2 in  return s1 > s2 })

Nếu chỉ có một câu lệnh return thì có thể bỏ chữ return đi

var reversedThings = things.sorted(by: { s1, s2 in s1 > s2 })

Bạn có thể gọi tham số của một function mà không cần biết tên bằng các từ khóa $0, $1, $2,… Khi đó, chúng ta thậm chí có thể viết closure ngắn hơn

var reversedThings = things.sorted(by: { $0  >  $1 })

Đến đây chắc hẳn các bạn đọc đã thấy việc viết anonymous function ngắn đến nhường nào, tuy nhiên Swift còn cho phép ta viết… ngắn hơn nữa cơ =))

var reversedThings = things.sorted(by: >)

Và Apple đã khiến các developer chúng ta trở nên nhây lười như thế đấy, bạn đọc có thể tự tìm hiểu với từ khóa “Operator Method”

Trailing syntax

Nếu một function có tham số cuối cùng là một closure thì ta có thể “bốc” closure đó ra cho vào block riêng của function nọ

var reversedThings = things.sorted { $0  >  $1 }

Hàm sorted khá ngắn nên chúng ta không thấy được sự khác biệt, nhưng chả hạn thử viết một closure dài lê thê

UIView.animate(withDuration: 3.0, animations: {
	view.center.x += 50
	view.center.y -= 50
	view.transform = CGAffineTransform.identity
})
UIView.animate(withDuration: 3.0) {
	view.center.x += 50
	view.transform = CGAffineTransform.identity
	view.center.y -= 50
}

Nhìn dễ hơn nhiều đúng không?

Đây chính là syntatic sugar (ý chỉ những tính năng của ngôn ngữ khiến code trở nên dễ đọc dễ hiểu hơn) mà các bác Apple đã nghĩ ra, để khiến Swift “đọc giống với ngôn ngữ tự nhiên” hơn, làm người code sướng hơn :V

Capture value

Một trong những tính chất ảo diệu cũng như quan trọng nhất của closure đó là closure có thể sử dụng biến local thuộc function ngoài kể cả khi function đó đã return

func buy(at place: String) -> (String) -> String {
	var things = [String]()
	return { thing in
		things.append(thing)
		return "Buy (things) at (place)"
	}
}

let buy1 = buy(at: "Dien may xanh")

let buy2 = buy(at: "The gioi di dong")

buy1("TV") // "Buy ["TV"] at Dien may xanh"

buy1("Tu lanh") // "Buy ["TV", "Tu lanh"] at Dien may xanh"

buy2("Oppo") // "Buy ["Oppo"] at The gioi di dong"

Wtf happened? Thực ra chẳng có phép màu nào ở đây cả. Khi bạn sử dụng tên một biến của function ngoài closure, closure sẽ “đánh cắp” biến đó và sử dụng như thể chính là một biến của closure (giống như mấy anh Hở Mông cướp vợ vậy)

Đây chính là lý do sinh ra cái tên closure (bao đóng)

Closure = Function + Môi trường xung quanh nơi nó sinh ra

(Gọi là “đánh cắp” cũng không đúng, quá trình diễn ra khá phức tạp, closure sẽ lưu lại một strong reference đến biến được gọi, vì biến đó vẫn còn được refer nên nó sẽ tồn tại kể cả khi function kết thúc. Mình sẽ giải thích chi tiết hơn trong một bài liên quan đến garbage collection)

For JavaScripter: Vì sự khác biệt trong cả scope của biến lẫn cách implementation hàm for nên bạn có thể sử dụng closure kết hợp với vòng lặp thoải con gà mái mà không phải dùng trick như JS

var a = [()->()]()

for i in 1...3 {
	a.append {
		print(i)
	}
}

for i in 4...6 {
	a[i-4]()
}

// in ra 1 2 3

Lời cuối

Closure được ứng dụng khá rộng rãi trong các hàm xử lí array (bộ ba map, filter, reduce), các hàm liên quan đến xử lí GUI và event, xử lí bất đồng bộ (networking). Việc biết được syntax cũng như hiểu magic của nó sẽ giúp bạn làm việc với closure mượt mà và đơn giẳn hơn

Vì tác giả khá là dị nên bài mở đầu không phải là bài giới thiệu, mong các bạn ném đá nhẹ tay.

Từ bài sau mình sẽ viết nghiêm túc. Mong các bạn đón đọc:

Swift (phần 1) — Đường hóa học đến từ Apple

Previous: [Android Tutorial] Bài 3: Giới thiệu về Button và cách xử lí sự kiện trên Button

Next: [Học PHP 7] Cài đặt môi trường