Một trong những vấn đề cơ bản trong lập trình ứng dụng là truyền data giữa các view controller. Có rất nhiều cách để làm điều này, như là dùng protocol, notification,… Ở bài viết này, mình sẽ giới thiệu đến 1 cách nữa, đó là sử dụng callback.
Bài viết này yêu cầu sự hiểu biết về closure. Nếu chưa hiểu về closure, bạn có thể đọc tại đây:
Nội dung bài viết
- Function type
- Callback là gì
- Lợi ích của callback
- Truyền data bằng cách sử dụng callback.
Function type:
Mọi function đều có 1 kiểu dữ liệu cụ thể, được tạo bởi kiểu dữ liệu của các tham số truyền vào và kiểu dữ liệu trả về của function đó.
Ví dụ ở func trên, không có tham số truyền vào và không có kiểu dữ liệu trả về, nên function type của func trên là () -> ().
Đối với func không có kiểu trả về, thì cũng có thể viết theo cách khác là func đó trả về kiểu Void.
func calculate(a: Int, b: Int) -> Int {
return a + b
}
// Function này có kiểu dữ liệu là (Int, Int) -> Int
Ở bài viết lần này, mình sẽ không nói sâu về function type, mà sẽ tập trung vào chủ đề passing data bằng callback. Vậy callback là gì?
Callback là gì?
- Callback có thể hiểu như là 1 closure được gán cho 1 biến.
- Để sử dụng callback truyền data, bạn khai báo callback với kiểu dữ liệu là kiểu dữ liệu của data mà bạn muốn truyền đi.
var onCalculate: (Int, Int) -> (Int)
Ở trên là ví dụ cách khai báo 1 callback có kiểu dữ liệu (Int, Int) -> Int. Callback này sẽ truyền data là 2 tham số kiểu Int đi, và khi 1 nơi khác nhận được data này, nó sẽ xử lí data và trả về 1 kiểu Int.
var onPrint: (String) -> Void
Ở ví dụ này, callback sẽ truyền data kiểu String đi, và khi 1 nơi khác nhận được dữ liệu, nó sẽ xử lí dữ liệu theo kiểu Void.
Note: Nếu muốn truyền đi dữ liệu kiểu khác, bạn chỉ cần đơn giản sửa callback thành kiểu dữ liệu bạn mong muốn truyền đi.
Truyền data sử dụng callback
Giả sử có 2 view controller như sau:
- View Controller 1 có 1 label để hiện thị kết quả, và 1 button để push sang View Controller 2.
- View Controller 2 có nhiệm vụ thực hiện tính toán tổng của 2 số kiểu Int và trả kết quả kiểu Int về cho View Controller 1 để hiển thị. -> VC2 muốn truyền đi 1 data kiểu Int.
- View Controller 1 sau khi nhận được data của VC2, sẽ hiển thị lên label.
VC1:
class FirstViewController: UIViewController {
@IBOutlet weak var myLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
@IBAction func didTapButtonGoNext(_ sender: Any) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let secondVC = storyboard.instantiateViewController(withIdentifier: "SecondViewController") as! SecondViewController
// Viết code hiển thị kết quả ở đây
navigationController?.pushViewController(secondVC, animated: true)
}
}
VC2:
class SecondViewController: UIViewController {
// 1
var completionSum: ((Int) -> (Void))?
override func viewDidLoad() {
super.viewDidLoad()
// 3
let result = calculate(num1: 9, num2: 8)
completionSum?(result)
// 4
navigationController?.popViewController(animated: true)
}
// 2
func calculate(num1: Int, num2: Int) -> Int {
return num1 + num2
}
}
- Khởi tạo 1 callback kiểu (Int) -> (Void), tức là callback này sẽ truyền đi data kiểu Int, và khi nhận được data sẽ xử lí theo kiểu Void.
- Khai báo func calculate để tính tổng 2 số và trả về kết quả kiểu Int.
- Tính tổng 2 số 9 và 8. Gọi completionSum?(result) để truyền đi result.
Ở đây có dấu ? ở callback vì callback này là kiểu optional. Khi callback chưa được khởi tạo thì trình biên dịch sẽ bỏ qua mà không gọi callback. Vì vậy hãy chắc chắn rằng bạn đã khởi tạo callback trước khi gọi chúng. - Back về VC1 để hiển thị kết quả.
Giờ thì quay trở lại VC1, add thêm đoạn code sau vào phần để trống để khởi tạo completionSum cho VC2, bằng cách gán completionSum bằng 1 closure có cùng kiểu dữ liệu.
secondVC.completionSum = { [weak self] (result) in
self?.myLabel.text = String(result)
}
Ở đây bạn đã viết đoạn code để xử lí dữ liệu nhận được từ VC2. Sau khi nhận được result kiểu Int, bạn sẽ xử lí dữ liệu theo kiểu Void bởi kiểu dữ liệu của callback là (Int) -> Void.
Khi VC2 gọi completionSum để truyền result đi, VC1 sẽ ngay lập tức nhận được và gọi hàm update label ở trên. Trình biên dịch sẽ chạy theo trình tự như sau:
- Bấm vào button 1 -> Khởi tạo VC2, khởi tạo completionSum cho VC2 -> push sang VC2.
- VC2 tính toán kết quả -> gọi completionSum -> VC1 nhận được kết quả và update cho label -> VC2 pop về VC1 để hiển thị kết quả.
Ngoài ra bạn có thể khởi tạo callback bằng cách gán callback bằng 1 func có sẵn có cùng kiểu dữ liệu.
Ở VC1, khai báo 1 func như sau:
func showResult(result: Int) {
myLabel.text = String(result)
}
Func này có kiểu dữ liệu (Int) -> Void, cùng kiểu dữ liệu với callback completionSum.
Sửa lại đoạn khai báo completionSum ở VC1 thành như sau:
secondVC.completionSum = showResult
Cách làm này cùng tác dụng với cách khai báo ở trên nhưng sẽ làm cho func ngắn gọn và clear hơn.
Lợi ích của việc dùng callback
- Đối với việc chỉ cần truyền những data đơn giản, xử lí đơn giản thì có thể dùng callback chứ không phải tạo protocol, notification,…
- Tính reuseable cao, và ngắn gọn.