Contents
Chào mừng bạn đã quay lại với seri Lập trình iOS cho mọi người.
Bài viết này sẽ đề cập tới 1 chủ đề khá kinh điển và sử dụng nhiều trong iOS. Đó là Delegation Pattern
, hay gọi theo tên dân gian là Delegate & DataSource. Trước tiên thì bạn cần phải nắm được các bài cơ bản sau:
Vì bản chất của Delegate thì là 1 Protocol, nên bạn phải thành thạo Protocol trước đã. Bước tiếp theo là áp dụng mô hình Delegation vào trong lập trình iOS.
Bắt đầu thôi!
Chuẩn bị
- MacOS 10.14.4
- Xcode 11.0
- Swift 5.1
1. Vấn đề
1.1. Vấn đề 1
Đây là một vấn đề khá là đau đầu trong lập trình.
- Tại View A, có 2 chức năng
- Mở View B
- Đóng View B
- Tại View B, có 2 chức năng
- Mở View A
- Đóng View A
Giải quyết cho vấn đề này thì phải
- Tạo con trỏ B trong A
- Tạo con trỏ A trong B
Khi đó thì tại mỗi View sẽ điều kiển được View kia và ngược lại. Nhưng vấn đó lại phát sinh khi close
hay huỷ đối tượng con trỏ kia. Dẫn tới deadlock
1.2. Vấn đề 2
Vấn đề này là quan hệ cha-con giữa các đối tượng:
- Tại View A, việc tạo đối tượng con (sub-view A) và truyền giá trị cho thuộc tính của nó (data) thì rất đơn giản.
- Sub-view A tạo ra và View A xét giá trị cho property của Sub-View A tại đó
- Tại Sub-View A, việc trả giá trị về View A thì không đơn giản như vậy
- Vì lúc này việc thực thi đang nằm tại function của Sub-View A và không có con trỏ của View A (con trỏ cha)
Giải quyết vấn đề này thì có thể áp dụng phương pháp trên để giải quyết. Tạo con trỏ cha trong đối tượng con. Và nó cũng gặp phải khó khăn như trên.
Bây giờ ta phải tìm cách giải quyết tối ưu hơn.
2. Delegation Pattern
Đầu tiên, delegate
là một design pattern
dùng để truyền dữ liệu giữa các class hoặc struct. Từ “delegate” ở đây có nghĩa là ủy quyền, uỷ thác, và để dễ hiểu hơn thì sẽ sử dụng những từ ngữ tương tự.
Ta phải giải quyết được 2 điểm:
- Truyền dữ liệu giữa các đối tượng
- Tránh việc deadlock khi các đối tượng release
2.1. Giải thích
- Khai báo 1 Protocol P
- Khai báo các function của P
- Tại View A thì implement P. Khi đó:
- A đã kế thừa thêm P
- Có thể hiểu A có thêm 1 kiểu dữ liệu nữa là
P
- A định nghĩa lại các function mà đã được khai báo tại P
- Tại Sub-View A, tại đó:
- Khai báo 1 property là
p
(có thể tên đặt delegate) với kiểu làP
- Khai báo 1 property là
- Tạo đối tượng Sub-View A
- Thực thi tại View A
- Các thuộc tính Sub-View A sẽ được View A gán giá trị cho
- Trong đó, gán
p = View A
(hay làself
) được chấp nhận vì View A đã implement Protocol P và xem như View A có kiểu dữ liệu làP
- Đây chính là chiều truyền dữ liệu thứ nhất:
Từ cha sang con
- Return Data
- Thực thi tại Sub-View A
- Sử dụng thuộc tính
p
(đang là thuộc tính trong class Sub-View A) để gọi các function của nó (đã khai báo tại Protocol P) - Dữ liệu sẽ truyền vào các
tham số
của các function được khai báo tại Protocol P - Đây chính là chiều truyền dữ kiệu thứ hai:
Từ con sang cha
- Vì
p
chính làView A
, nên- p gọi function của nó với các đối số là dữ liệu từ Sub-View A
- cũng chính là View A gọi các function mà đã implement Protocol P, với các đối số là dữ liệu từ Sub-View A
Về bản chất
p
cũng chính là con trỏView A
nên sẽ thực thi được các function mong muốn và dữ liệu được truyền đi dựa theo việc gán các đối số cho các function mong muốn đó.
2.2. Sử dụng
- Đâu tiên sử dụng Protocol để khai báo các Delegate.
- Bước 1: Khai báo Delegate
protocol SubViewADelegate { func passData(data: String) }
- Bước 2: tạo các class View A và Sub-View A
//MARK: Class View A class ViewA { init() { } } //MARK: Class Sub-View A class SubViewA { init() { } }
- Bước 3: khai báo delegate cho Sub-View A
- Delegte phải được khai báo là
optional
- Delegte phải được khai báo là
//MARK: Class Sub-View A class SubViewA { var delegate: SubViewADelegate? init() { } }
- Bước 4: Implement Protocol cho View A
- Định nghĩa lại các function của Delegate
class ViewA: SubViewADelegate { init() { } //MARK: SubviewA Delegate func passData(data: String) { print("Data: \(data)") } }
- Bước 5: thực thi truyền dữ liệu từ Sub-View A sang View A
//MARK: Do something var viewA = ViewA() var subViewA = SubViewA() subViewA.delegate = viewA //pass data subViewA.delegate?.passData(data: "do something")
- Kết quả
Làm đẹp một chút cho nó nhìn chuyên nghiệp hơn.
//MARK: Define delegate protocol SubViewADelegate { func passData(data: String) } //MARK: Class View A class ViewA { init() { } } //SubviewA Delegate extension ViewA: SubViewADelegate { func passData(data: String) { print("Data: \(data)") } } //MARK: Class Sub-View A class SubViewA { var delegate: SubViewADelegate? init() { } // action func doSomething(data: String) { if let delegate = delegate { delegate.passData(data: data) } } } //MARK: Do something var viewA = ViewA() var subViewA = SubViewA() subViewA.delegate = viewA //do something subViewA.doSomething(data: "OK, let's me go")
Trong đó:
- Viết
extension
cho View A để implement delegate - Viết function xử lý và truyền dữ liệu tại Sub-View A & kiểm tra delegate
3. Delegation trong View
Delegate và Datasource xuất hiện ở mọi ngóc ngách trong hệ sinh thái iOS, và phần lớn các developer đơn giản là chỉ copy & paste
chúng để dùng mà không biết rõ bản chất hoạt động là ra sao. Sau đây, mình sẽ nói về cách sử dụng nó trong code iOS.
3.1. UI Control cơ bản
Hầu hết tất cả UI Control cơ bản đều có delegate
và datasource
của riêng nó. Tại sao vậy?
Delegate của UI Control nhằm thông báo cho các View hay Controller biết sự thay đổi về
trạng thái
của chính bản thân nó.
Ví dụ:
Khi dùng tay tác động để kéo 1 scrollview và thả ra. Thì scrollview vẫn tiếp tục chạy 1 tí, mặc dù người dùng còn tác động nữa. Đó chính là sự thay đổi trạng thái của scrollview. Cho tới lúc scrollview dừng và người dùng muốn bắt được sự kiện đó thì scroll view phải gởi đi 1 sự kiện bằng delegate
của nó. Nếu View hay Controller implement và lắng nghe sự kiện từ delegate của scrollview thì sẽ nhận được .
Làm thế nào để tìm ra tụi nó?
Ví dụ với UITableViewDelegate
> nhấn command
+ kích chuột.
Nhảy trực tiếp tới file define các function của Delegate đó.
Copy ra và dùng tại class mà implement Delegate đó.
Đây cũng chính là cách bắt sự kiện tiếp theo (ngoài IBAction, Touch Event và cảm ứng) trong lập trình iOS.
3.2. Custom View
Phần này sẽ hướng dẫn chính về sử dụng delegate trong custom view. Mục đích:
-
- Truyền dữ liệu từ custom view về View Controller
- Không cần phải phải tạo các property để quản lý các custom view như là 1 biến toàn cục của class ViewController
- Sử dụng được với nhiều đối tượng custom tạo ra
Tiến hành tạo 1 custom view với phương pháp LoadNibName
. Nếu bạn chưa biết thì có thể đọc bài custom view:
Trong đó:
- UserView
- Là 1 sub-class của UIView
- Có 1 UIImageView và 1 UILabel
Khai báo thêm một Protocol cho UserView:
- Đặt tên là UserViewDelegate
- Khai báo 1 function với mục đích trả về thứ tự của view khi người dùng chạm vào.
protocol UserViewDelegate: class { func userView(userView: UserView, didSelectedWith index: Int) }
Triển khai sử dụng trên custom view
class UserView: UIView { weak var delegate: UserViewDelegate? var index: Int = 0 @IBOutlet weak var avatarImageView: UIImageView! @IBOutlet weak var nameLabel: UILabel! @IBAction func tap(_ sender: Any) { if let delegate = delegate { delegate.userView(userView: self, didSelectedWith: index) } } }
Trong đó:
- Khai báo property delegate với từ khoá
weak
và kiểuoptional
- Điều này tránh
leak
bộ nhớ khi đối tượng custom view bị giải phỏng thì delegate cũng bị giải phóng theo - Khi sử dụng
delegate
để đảm bảo cho chương trình không bị crash thì bên kiểm tra sự tồn tại của chúng.
Tại file ViewController, tạo custom view và addSubview
import UIKit class HomeViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() let userView = Bundle.main.loadNibNamed("UserView", owner: self, options: nil)?.first as! UserView userView.frame = CGRect(x: 50, y: 50, width: 100, height: 125) userView.index = 10 view.addSubview(userView) } }
Viết extension
của ViewController cho việc implement Delegate. Ở đây, định nghĩa lại các function của delegate.
extension HomeViewController: UserViewDelegate { func userView(userView: UserView, didSelectedWith index: Int) { print("Did select UserView with index \(index)") } }
Tiếp theo, xét giá trị delegate của custom view chính là ViewController
userView.delegate = self
Kết quả:
Bạn để ý thì sẽ thấy:
- Biến
userView
được tạo ra và addSubview vào view của ViewController thì kết thúc hàmviewDidLoad
nó sẽ bị giải phỏng khỏi bộ nhớ- Tuy nhiên, delegate của nó đã trỏ tới ViewController nên chúng ta vẫn có thể quản lý và nhận dữ liệu từ Custom View
Ngoài việc sử dụng với Custom View thì delegate được sử dụng ở hầu hết các class/struct/enum. Điều này nhăm gia tăng sức mạnh của class của bạn.
4. DataSource
Theo các phần trên thì chúng ta mới đi được 1 chiều là:
Truyền dữ liệu từ đối tượng con sang đối tượng cha.
Vậy khi đối tượng con muốn lấy giá trị từ đối tượng cha thì sẽ như thế nào?
Có một khái niệm được đưa ra là
datasource
với ý nghĩa đảo ngược lại quá trình delegate.
Phần tích một chút về delagate:
- function được thực thi và dữ liệu được gán vào các tham số của function
- các function thường sẽ return về là
Void
Đảo ngược lại quá trình delegate thì sẽ như thế nào? Tham khảo đoạn code sau:
protocol UserViewDataSource: class { func userView(nameOf userView: UserView) -> String func userView(indexOf userView: UserView) -> Int }
Trong đó:
- Function return về kiểu dữ liệu khác
Void
- Cách khai báo DataSource vẫn giống như Delegate
- Các function chỉ khai báo
Sử dụng trong custom view
- Khai báo property
dataSource
weak var dataSource: UserViewDataSource?
- Sử dụng dataSource tại Custom View, cũng tương tự như delegate.
func configView() { if let dataSource = dataSource { //set index index = dataSource.userView(indexOf: self) //set name nameLabel.text = dataSource.userView(nameOf: self) } }
Tại file ViewController:
- implement
UserViewDataSource
bằng cách tạo 1extension
cho ViewController.
extension HomeViewController: UserViewDataSource { func userView(nameOf userView: UserView) -> String { return "Fx Studio" } func userView(indexOf userView: UserView) -> Int { return 999 } }
- Xét giá trị cho property
dataSource
của Custom View là ViewController
userView.dataSource = self
- Gọi hàm
configView()
của Custom View để thay đổi dữ liệu của Custom View.
Xem lại toàn bộ code của ViewController và chạy chương trình để kiểm tra:
import UIKit class HomeViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() let userView = Bundle.main.loadNibNamed("UserView", owner: self, options: nil)?.first as! UserView userView.frame = CGRect(x: 50, y: 150, width: 100, height: 125) userView.index = 10 userView.delegate = self userView.dataSource = self view.addSubview(userView) userView.configView() } } extension HomeViewController: UserViewDelegate { func userView(userView: UserView, didSelectedWith index: Int) { print("Did select UserView with index \(index)") } } extension HomeViewController: UserViewDataSource { func userView(nameOf userView: UserView) -> String { return "Fx Studio" } func userView(indexOf userView: UserView) -> Int { return 999 } }
Kết quả:
- Ban đầu Custom View được tạo và addSubview vào View của ViewController với
index = 10
- Gọi hàm
configView()
thì giá trị củaindex
vàname
đã được thay đổi - Khi click vào Custom View thì
delegate
sẽ truyền dữ liệuindex
về ViewController và in ra - Bây giờ giá trị của
index = 999
Qua ví dụ trên thì ta thấy
delegate
vàdatasource
thực chất đều là Protocol. Nhưng tuỳ vào cách sử dụng là truyền hay lấy dữ liệu thì chúng ta sẽ có tên gọi riêng cho nó.
- Truyền dữ liệu : Delegate
- Lấy dữ liệu: DataSource
Tạm kết
- Nắm được các vấn đề trong việc truyền dữ liệu
- Hiểu được mô hình Delegation
- Khai báo và sử dụng các Delagate & DataSource Protocol
- Nắm được sự khác nhau cơ bản của Delegate và DataSource
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!
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)