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 một đối tượng khác thông qua một giao thức với các phương thức được xác định trước. Trong phát triển iOS, Delegate là một trong những mẫu cơ bản của Cocoa Touch, được sử dụng để thông báo không đồng bộ mà không có sự liên kết trực tiếp giữa người gửi và người nhận. Theo Apple Documentation (2025), ủy quyền được sử dụng trong Foundation và UIKit để xử lý các sự kiện bảng, yêu cầu mạng và quản lý vị trí. Mẫu này đảm bảo sự liên kết lỏng lẻo của các thành phần và tái sử dụng mã.
Những điểm chính
Delegate (người được ủy quyền) là một đối tượng triển khai một giao thức cụ thể và nhận thông báo về các sự kiện của một đối tượng khác. Mẫu ủy quyền là một giải pháp thay thế cho kế thừa: thay vì tạo một lớp con để ghi đè các phương thức, một đối tượng ủy quyền xử lý sự kiện cho một đối tượng bên ngoài. Trong iOS, ủy quyền được triển khai thông qua các giao thức Swift với các phương thức bắt buộc và tùy chọn. Thuộc tính delegate luôn được khai báo là weak var để tránh tham chiếu vòng giữa các đối tượng.
Một giao thức delegate định nghĩa hợp đồng tương tác giữa các đối tượng. Các phương thức bắt buộc phải được triển khai bởi người được ủy quyền, nếu không mã sẽ không biên dịch được. Các phương thức tùy chọn được đánh dấu bằng thuộc tính @objc optional và cho phép người được ủy quyền chỉ phản hồi các sự kiện liên quan. Tên phương thức tuân theo quy ước: tham số đầu tiên là đối tượng gửi, tham số thứ hai là dữ liệu sự kiện. Ví dụ, tableView(_:didSelectRowAt:) chỉ ra rằng người gửi là UITableView và dữ liệu là chỉ số của hàng được chọn.
// Giao thức Delegate
protocol DownloadManagerDelegate: AnyObject {
func downloadManager(_ manager: DownloadManager,
didFinishWith data: Data)
func downloadManager(_ manager: DownloadManager,
didFailWith error: Error)
@objc optional func downloadManager(_ manager: DownloadManager,
didUpdateProgress progress: Float)
}
// Lớp sử dụng Delegate
class DownloadManager {
weak var delegate: DownloadManagerDelegate?
func startDownload(from url: URL) {
URLSession.shared.dataTask(with: url) { [weak self] data, _, error in
guard let self else { return }
if let error = error {
self.delegate?.downloadManager(self, didFailWith: error)
} else if let data = data {
self.delegate?.downloadManager(self, didFinishWith: data)
}
}.resume()
}
}
Thuộc tính delegate phải được khai báo là weak var để ngăn chặn retain cycle. Nếu tham chiếu là mạnh, người được ủy quyền và đối tượng ủy quyền sẽ giữ nhau, và ARC sẽ không thể giải phóng bộ nhớ của chúng. Các giao thức delegate kế thừa AnyObject (chỉ lớp), cho phép sử dụng weak. Cấu trúc và kiểu liệt kê không thể là người được ủy quyền do ngữ nghĩa giá trị. Một giải pháp thay thế cho kiểu giá trị là callback closures.
class ViewController: DownloadManagerDelegate {
let manager = DownloadManager()
override func viewDidLoad() {
super.viewDidLoad()
manager.delegate = self // weak — không có retain cycle
manager.startDownload(from: url)
}
func downloadManager(_ manager: DownloadManager, didFinishWith data: Data) {
processData(data)
}
func downloadManager(_ manager: DownloadManager, didFailWith error: Error) {
showError(error)
}
}
Mẫu Delegate hoạt động theo nguyên tắc một-một: một đối tượng gửi chỉ có thể có một người được ủy quyền tại một thời điểm nhất định. Khi một sự kiện xảy ra, người gửi kiểm tra xem người được ủy quyền đã được thiết lập chưa và gọi phương thức giao thức tương ứng. Lợi thế so với các cuộc gọi trực tiếp là người gửi không biết kiểu của người được ủy quyền, chỉ biết rằng nó tuân thủ giao thức. Điều này tuân thủ Nguyên tắc Đảo ngược Phụ thuộc (DIP) từ SOLID.
Người được ủy quyền được chỉ định thông qua phép gán: someObject.delegate = self. Khi người được ủy quyền bị hủy cấp phát, thuộc tính tự động trở thành nil do ngữ nghĩa weak. Trước khi gọi một phương thức của người được ủy quyền, người được ủy quyền được kiểm tra thông qua chuỗi tùy chọn: delegate?.method(). Nếu delegate là nil, cuộc gọi bị bỏ qua mà không bị crash. Đối với các phương thức tùy chọn của giao thức, một kiểm tra bổ sung được sử dụng: delegate?.responds(to: #selector(...)), mặc dù trong Swift, kiểm tra này thường được thực hiện ngầm thông qua khai báo phương thức tùy chọn.
Trong môi trường đa luồng, delegate được sử dụng để trả về kết quả không đồng bộ. URLSession cung cấp URLSessionDelegate với các phương thức được gọi khi nhận được dữ liệu, hết thời gian chờ hoặc lỗi xác thực. Các phương thức delegate được thực thi trong hàng đợi nền của URLSession, do đó cần phải điều phối đến hàng đợi chính để cập nhật giao diện người dùng. Delegate không đồng bộ không chặn luồng gọi, cho phép tiếp tục các tác vụ khác.
class NetworkService: NSObject, URLSessionDataDelegate {
private lazy var session = URLSession(
configuration: .default,
delegate: self,
delegateQueue: OperationQueue()
)
private var receivedData = Data()
func urlSession(_ session: URLSession,
dataTask: URLSessionDataTask,
didReceive data: Data) {
receivedData.append(data)
let progress = Float(receivedData.count) / Float(expectedSize)
DispatchQueue.main.async {
self.progressHandler?(progress)
}
}
func urlSession(_ session: URLSession,
task: URLSessionTask,
didCompleteWithError error: Error?) {
if let error = error {
delegate?.networkService(self, didFailWith: error)
} else {
delegate?.networkService(self, didReceive: receivedData)
}
}
}
Delegate và Callback giải quyết cùng một vấn đề — thông báo không đồng bộ — nhưng theo những cách khác nhau. Delegate sử dụng một giao thức với các phương thức được đặt tên, Callback sử dụng một closure với việc nắm bắt ngữ cảnh. Sự lựa chọn phụ thuộc vào số lượng sự kiện, độ phức tạp của chữ ký và sở thích kiến trúc. Apple khuyến nghị Delegate cho các API có nhiều sự kiện (UITableView — 20+ phương thức) và Callback cho các hoàn thành một lần.
Delegate được ưu tiên khi xử lý nhiều sự kiện khác nhau từ một nguồn duy nhất. Ví dụ, CLLocationManager thông báo cho người được ủy quyền của nó về các thay đổi vị trí, lỗi quyền, vào/ra khỏi hàng rào địa lý và thay đổi trạng thái dịch vụ. Mỗi sự kiện là một phương thức giao thức riêng biệt với tên rõ ràng và tham số được định kiểu. Delegate cũng thuận tiện cho việc cấu hình hành vi (các phương thức should, will, did).
Callback đơn giản hơn cho các yêu cầu một lần với một kết quả duy nhất. Completion handler trong URLSession.dataTask chiếm một dòng tại điểm gọi so với ít nhất ba phương thức giao thức. Callback cũng tự nhiên hơn cho các chuỗi chức năng (map, flatMap, async/await). Tuy nhiên, với việc lồng nhau vượt quá 2-3 cấp độ, callback trở thành Callback Hell, trong khi delegate luôn giữ phẳng.
iOS SDK chứa hàng chục giao thức delegate tích hợp cho các hệ thống con khác nhau. Mỗi giao thức được thiết kế cho một kịch bản tương tác cụ thể. Theo Apple Documentation (2025), các delegate được sử dụng nhiều nhất là UITableViewDelegate, UITextFieldDelegate, CLLocationManagerDelegate, URLSessionDelegate và UNUserNotificationCenterDelegate. Các giao thức này chứa từ 3 đến 30 phương thức với các mức độ bắt buộc khác nhau.
UITableViewDelegate quản lý giao diện và hành vi của các ô bảng. Nó chứa các phương thức để xử lý lựa chọn hàng, cấu hình chiều cao ô, chế độ xem tiêu đề/chân trang tùy chỉnh và các hành động vuốt. Tất cả các phương thức của giao thức đều là tùy chọn, cho phép chỉ triển khai chức năng cần thiết. Nếu không có delegate, bảng hoạt động với cài đặt mặc định. Trong lịch sử, delegate được kết hợp với UITableViewDataSource.
URLSessionDelegate cung cấp kiểm soát chi tiết đối với các yêu cầu HTTP. Các phương thức delegate được gọi khi nhận được phản hồi từ máy chủ, dữ liệu đến hoặc tải xuống hoàn tất. Các giao thức con chuyên biệt URLSessionTaskDelegate và URLSessionDataDelegate mở rộng chức năng cơ bản cho các loại tác vụ cụ thể. Delegate cần thiết để hỗ trợ tải xuống nền, chứng chỉ SSL và xử lý chuyển hướng tùy chỉnh.
| Delegate | Phương thức | Mục đích |
|---|---|---|
| UITableViewDelegate | 25 | Giao diện và tương tác bảng |
| UITextFieldDelegate | 8 | Xử lý nhập văn bản và bàn phím |
| CLLocationManagerDelegate | 12 | Cập nhật vị trí và hàng rào địa lý |
| URLSessionDelegate | 6 | Quản lý phiên HTTP và chứng chỉ |
| UNUserNotificationCenterDelegate | 4 | Xử lý thông báo đẩy ở nền trước |
Quản lý bộ nhớ là một khía cạnh quan trọng khi làm việc với delegate trong iOS. ARC (Đếm tham chiếu tự động) tự động quản lý bộ nhớ, nhưng chỉ với việc sử dụng đúng các tham chiếu weak/unowned. Vi phạm các quy tắc dẫn đến rò rỉ bộ nhớ hoặc hủy cấp phát sớm. Một delegate được khai báo là strong tạo ra một retain cycle nếu chủ sở hữu delegate cũng giữ một tham chiếu đến đối tượng ủy quyền.
Retain cycle xảy ra khi đối tượng A (chủ sở hữu) tự đặt mình làm delegate của đối tượng B, và B giữ một tham chiếu mạnh đến delegate. Ví dụ: ViewController tạo URLSession, tự đặt mình làm delegate của phiên, nhưng URLSession theo mặc định giữ một tham chiếu mạnh đến delegate nếu delegateQueue không được chỉ định. Giải pháp là luôn kiểm tra tài liệu API về loại tham chiếu delegate (weak hoặc strong) và đặt rõ ràng delegate thành nil trong deinit.
class SafeViewController: UIViewController {
private var session: URLSession?
private var service: NetworkService?
override func viewDidLoad() {
super.viewDidLoad()
service = NetworkService()
service?.delegate = self
}
deinit {
// Đặt delegate thành nil trong deinit — best practice
service?.delegate = nil
session?.invalidateAndCancel()
}
}
// URLSession với weak delegate qua NSObject
class WeakDelegateSession: NSObject {
private weak var delegate: URLSessionDelegate?
func createSession() -> URLSession {
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 1
return URLSession(
configuration: .default,
delegate: self,
delegateQueue: queue
)
}
}
Trước khi gọi một phương thức delegate, bạn phải xác minh rằng delegate tồn tại (không nil) và triển khai phương thức được gọi. Đối với các phương thức bắt buộc của giao thức, không cần kiểm tra — trình biên dịch đảm bảo việc triển khai. Đối với phương thức tùy chọn, hãy sử dụng respond(to:) hoặc chuỗi tùy chọn. Nếu delegate bị hủy cấp phát, tham chiếu weak tự động trở thành nil và cuộc gọi delegate bị bỏ qua. Đây là hành vi an toàn không yêu cầu xử lý bổ sung.
Các nhà phát triển thường mắc lỗi khi làm việc với mẫu Delegate, đặc biệt là trong giai đoạn đầu học iOS. Phổ biến nhất bao gồm: retain cycle do delegate strong, quên gọi delegate?.method(), chữ ký phương thức giao thức sai, đặt delegate sau khi bắt đầu một thao tác và xung đột đa luồng. Hãy xem xét từng lỗi và cách phòng tránh.
Lỗi nghiêm trọng nhất là khai báo thuộc tính delegate là strong var thay vì weak var. Điều này tạo ra một retain cycle mà cả delegate và đối tượng ủy quyền đều không thể được giải phóng. Hậu quả: rò rỉ bộ nhớ, ứng dụng chậm và lỗi ẩn. Giải pháp: luôn sử dụng weak var cho delegate và cho giao thức kế thừa từ AnyObject để ngăn chặn việc sử dụng kiểu giá trị làm delegate.
Nếu delegate được đặt sau khi gọi một phương thức không đồng bộ, các sự kiện đầu tiên có thể bị mất. Ví dụ: gọi startDownload() trước khi gán manager.delegate = self dẫn đến mất callback hoàn thành nếu tải xuống được thực thi đồng bộ hoặc rất nhanh. Giải pháp: đặt delegate trước khi gọi phương thức không đồng bộ và ghi lại thứ tự khởi tạo trong các chú thích giao thức.
Câu hỏi thường gặp
Weak ngăn chặn retain cycle giữa delegate và đối tượng ủy quyền. Nếu tham chiếu là mạnh, các đối tượng sẽ giữ nhau và ARC không thể giải phóng chúng. Một tham chiếu weak tự động trở thành nil khi delegate bị hủy cấp phát. Đây là thực hành tiêu chuẩn của Cocoa Touch kể từ khi Objective-C ra đời và được giữ lại trong Swift để tương thích ngược.
Delegate xử lý các sự kiện và quản lý hành vi (chiều cao ô, phản hồi khi chạm). DataSource cung cấp dữ liệu để hiển thị (số lượng hàng, ô). Delegate trả lời câu hỏi “như thế nào?”, dataSource trả lời câu hỏi “cái gì?”. Trong iOS, cả hai đều được triển khai thông qua các giao thức, thường trong cùng một bộ điều khiển, nhưng về mặt khái niệm là riêng biệt.
Không, nếu giao thức kế thừa từ AnyObject (giao thức lớp). Tham chiếu weak chỉ khả dụng cho kiểu tham chiếu (lớp). Đối với kiểu giá trị (struct, enum), hãy sử dụng callback closures hoặc một lớp wrapper riêng. Nếu bạn kiểm soát giao thức, bạn có thể tránh kế thừa AnyObject, nhưng khi đó weak bị cấm — hãy lựa chọn có ý thức giữa delegate weak và delegate struct.
responds(to:) là một phương thức của NSObjectProtocol kiểm tra xem một đối tượng có triển khai bộ chọn được chỉ định hay không. Nó được sử dụng để kiểm tra các phương thức tùy chọn @objc của giao thức trước khi gọi. Nếu không có kiểm tra này, việc gọi một phương thức tùy chọn chưa được triển khai sẽ dẫn đến NSInvalidArgumentException. Trong Swift, đối với các giao thức có @objc optional, việc kiểm tra có thể được thực hiện ngầm thông qua optional binding.
Không, delegate là một mẫu ủy quyền, không phải singleton. Không giống như singleton, một delegate có thể được thay thế trong thời gian chạy và tồn tại trong một thể hiện duy nhất cho mỗi đối tượng ủy quyền. Một đối tượng có thể là delegate cho nhiều người gửi. Singleton là một mẫu tạo ra đảm bảo một thể hiện lớp duy nhất, không liên quan gì đến ủy quyền.
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