Delegate là một mẫu thiết kế trong đó một đối tượng ủy quyền thực thi tác vụ cho đối tượng khác. Trong iOS, mẫu này được triển khai thông qua giao thức Swift và @protocol Objective-C. Ủy quyền là một trong những mẫu cơ bản của Cocoa Touch, được sử dụng trong UITableViewDelegate, UITextFieldDelegate và hàng trăm API Apple khác. Theo Tài liệu nhà phát triển Apple (2025), khoảng 70% lớp hệ thống UIKit sử dụng delegate để tùy chỉnh hành vi mà không cần kế thừa.
Những điểm chính
Delegate là một mẫu thiết kế hành vi cho phép một đối tượng ủy quyền một phần trách nhiệm của mình cho đối tượng khác. Không giống như kế thừa, nơi lớp con ghi đè phương thức của lớp cha, delegate sử dụng thành phần (composition): đối tượng sở hữu giữ một tham chiếu đến delegate và gọi các phương thức của nó tại những điểm cụ thể.
Delegate định nghĩa một giao thức — một tập hợp các phương thức mà delegate có thể triển khai. Các phương thức được chia thành bắt buộc (required) và tùy chọn (optional). Trong Swift, các phương thức giao thức tùy chọn được đánh dấu bằng từ khóa @objc optional.
Đối tượng A (chủ sở hữu) chứa thuộc tính delegate — một tham chiếu yếu đến đối tượng B (delegate). Khi một sự kiện xảy ra, A kiểm tra xem B có triển khai phương thức giao thức tương ứng không và gọi nó. Tham chiếu yếu là bắt buộc: nếu không có nó, delegate không thể được giải phóng khỏi bộ nhớ vì chủ sở hữu giữ nó bằng tham chiếu mạnh.
protocol LoaderDelegate: AnyObject {
func loaderDidStart(_ loader: DataLoader)
func loader(_ loader: DataLoader, didLoad data: Data)
func loader(_ loader: DataLoader, didFailWith error: Error)
}
class DataLoader {
weak var delegate: LoaderDelegate?
func start() {
delegate?.loaderDidStart(self)
// tải bất đồng bộ
}
}Lớp DataLoader định nghĩa giao thức LoaderDelegate và gọi các phương thức delegate tại những điểm chính của vòng đời tải. AnyObject đảm bảo rằng giao thức chỉ có thể được triển khai bởi các lớp — điều này cần thiết cho tham chiếu yếu.
Triển khai delegate trong Swift bao gồm ba bước: khai báo giao thức, tạo thuộc tính delegate yếu trong chủ sở hữu và triển khai giao thức trong lớp delegate. Hãy xem xét ví dụ về UITextField tùy chỉnh với xác thực.
protocol ValidatorDelegate: AnyObject {
func validate(_ input: String) -> Bool
func validatorDidFail(_ input: String)
}
class ValidatedTextField: UITextField {
weak var validator: ValidatorDelegate?
override func textDidChange() {
guard let text = self.text else { return }
if validator?.validate(text) == false {
validator?.validatorDidFail(text)
self.layer.borderColor = UIColor.red.cgColor
}
}
}
class LoginViewController: UIViewController, ValidatorDelegate {
let textField = ValidatedTextField()
override func viewDidLoad() {
super.viewDidLoad()
textField.validator = self
}
func validate(_ input: String) -> Bool {
return input.count >= 6
}
func validatorDidFail(_ input: String) {
print("Xác thực thất bại: đầu vào quá ngắn")
}
}LoginViewController triển khai giao thức ValidatorDelegate và tự gán mình làm delegate cho textField. Mỗi khi văn bản thay đổi, ValidatedTextField gọi validate(_:), và nếu xác thực thất bại — validatorDidFail(_:). Bộ điều khiển đóng vai trò trung gian giữa View và logic xác thực.
Việc lựa chọn giữa delegate, thông báo và closure phụ thuộc vào số lượng người nhận và sự kết nối giữa các thành phần. Mỗi cơ chế giải quyết vấn đề giao tiếp giữa các đối tượng, nhưng với những đánh đổi khác nhau.
| Đặc điểm | Delegate | NotificationCenter | Closure |
|---|---|---|---|
| Loại giao tiếp | 1:1 | 1:N | 1:1 |
| Kết nối | Yếu (thông qua giao thức) | Rất yếu (khóa chuỗi) | Trung bình (capture ngữ cảnh) |
| An toàn kiểu | Đầy đủ | Không có (Any?) | Đầy đủ |
| Rủi ro retain cycle | Không (weak) | Không | Có (capture self) |
| Khi nào sử dụng | Callback phức tạp với nhiều phương thức | Sự kiện mà nhiều đối tượng quan tâm | Closure đơn giản với 1-2 callback |
Delegate là tối ưu khi bạn cần truyền một loạt sự kiện liên quan đến một người nhận duy nhất. NotificationCenter tốt hơn cho thông báo broadcast. Closure dành cho các hoạt động bất đồng bộ đơn giản, như trình xử lý hoàn thành trong URLSession.
Objective-C sử dụng @protocol và @optional để khai báo delegate. Không giống Swift, tất cả các phương thức giao thức đều là tùy chọn theo mặc định. Sự khác biệt chính là lời gọi respondsToSelector: trước khi gửi thông điệp đến delegate, vì phương thức có thể không được triển khai.
@protocol ImageCacheDelegate
@optional
- (void)cacheDidStartDownload: (ImageCache *)cache;
- (void)cache: (ImageCache *)cache didCacheImage: (UIImage *)image;
@required
- (void)cache: (ImageCache *)cache didFailWithError: (NSError *)error;
@end
@interface ImageCache : NSObject
@property (nonatomic, weak) id<ImageCacheDelegate> delegate;
- (void)downloadImageAtURL: (NSURL *)url;
@end
@implementation ImageCache
- (void)downloadImageAtURL: (NSURL *)url {
if ([self.delegate respondsToSelector:@selector(cacheDidStartDownload:)]) {
[self.delegate cacheDidStartDownload:self];
}
// tải hình ảnh bất đồng bộ
}
@endSự khác biệt chính trong Objective-C: trước khi gọi một phương thức tùy chọn, cần phải kiểm tra respondsToSelector:. Trong Swift, các phương thức giao thức tùy chọn loại bỏ việc kiểm tra này — optional chaining (?.) tự động xử lý trường hợp không có triển khai.
Lỗi sử dụng delegate dẫn đến rò rỉ bộ nhớ, ứng dụng bị treo và các lỗi khó phát hiện. Hãy xem xét năm vấn đề phổ biến nhất.
Retain cycle là lỗi phổ biến nhất. Nếu thuộc tính delegate được khai báo là strong và delegate lại sở hữu đối tượng chủ sở hữu, một vòng giữ chặt được hình thành. Cả hai đối tượng sẽ không bao giờ được giải phóng khỏi bộ nhớ. Giải pháp: luôn khai báo delegate là weak var trong Swift hoặc @property (weak) trong Objective-C.
Nếu đối tượng chủ sở hữu tồn tại lâu hơn delegate và tham chiếu vẫn còn, việc gọi phương thức delegate sẽ dẫn đến EXC_BAD_ACCESS. Tham chiếu yếu giải quyết vấn đề này tự động: sau khi delegate được giải phóng, thuộc tính trở thành nil. Tuy nhiên, trong các kịch bản đa luồng, bạn nên kiểm tra thêm delegate trên luồng chính.
Một giao thức với hơn 20 phương thức vi phạm Nguyên tắc phân tách giao diện (ISP). UITableViewDelegate chứa khoảng 30 phương thức tùy chọn — đây là một ngoại lệ lịch sử. Trong các giao thức của riêng bạn, tốt hơn nên chia trách nhiệm thành nhiều giao thức nhỏ hơn, mỗi giao thức có vai trò riêng.
API hệ thống của Apple tích cực sử dụng mẫu Delegate. Hãy xem ba ví dụ chính từ UIKit xuất hiện trong mọi ứng dụng iOS.
| API | Giao thức | Phương thức chính |
|---|---|---|
| UITableView | UITableViewDelegate | didSelectRowAt, heightForRowAt, willDisplay |
| UITextField | UITextFieldDelegate | shouldChangeCharactersIn, didBeginEditing, shouldReturn |
| URLSession | URLSessionDelegate | didReceiveChallenge, didCompleteWithError, didBecomeInvalidWithError |
Mỗi giao thức này triển khai các khía cạnh khác nhau của hành vi: UITableViewDelegate quản lý giao diện và phản hồi chạm, UITextFieldDelegate kiểm soát nhập văn bản, URLSessionDelegate xử lý các sự kiện mạng. Điều này cho thấy tính linh hoạt của mẫu: delegate có thể được điều chỉnh cho bất kỳ lĩnh vực trách nhiệm nào.
Câu hỏi thường gặp
Delegate quản lý hành vi và giao diện (chiều cao ô, phản hồi chạm). DataSource cung cấp dữ liệu (số hàng, nội dung ô). Trong UITableViewDelegate và UITableViewDataSource — đây là hai giao thức riêng biệt phân chia trách nhiệm giữa trình bày và dữ liệu.
Tham chiếu yếu ngăn chặn retain cycle. Chủ sở hữu (ví dụ: UITableView) chỉ giữ tham chiếu đến delegate dưới dạng weak. Nếu delegate (UIViewController) sở hữu bảng, một tham chiếu mạnh đến delegate sẽ tạo ra vòng lặp: ViewController → UITableView → Delegate (ViewController). Weak phá vỡ vòng lặp này.
Trong SwiftUI, mẫu Delegate được sử dụng ít thường xuyên hơn — nó được thay thế bằng @Binding, @State và closure. Tuy nhiên, delegate vẫn được sử dụng để tích hợp UIKit thông qua UIViewRepresentable. Ví dụ: MKMapViewDelegate và WKUIDelegate vẫn phù hợp khi bao bọc các thành phần UIKit trong SwiftUI.
@objc optional cho phép khai báo các phương thức tùy chọn trong giao thức Swift. Đây là cơ chế tương thích với môi trường thực thi Objective-C. Nếu không có @objc, tất cả các phương thức giao thức Swift đều bắt buộc theo mặc định. Optional được sử dụng trong các giao thức UIKit, nơi delegate chỉ có thể triển khai những phương thức cần thiết.
Một đối tượng có thể có chỉ một delegate cho mỗi thuộc tính delegate. Nếu bạn cần thông báo cho nhiều đối tượng, hãy sử dụng multicast delegate, một mảng các delegate hoặc NotificationCenter. Mẫu Delegate được thiết kế ban đầu như một mối quan hệ 1:1.
Tổng kết
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm