Delegate คือรูปแบบการออกแบบที่วัตถุหนึ่งมอบหมายการดำเนินงานให้กับอีกวัตถุหนึ่งผ่านโปรโตคอลที่มีเมธอดที่กำหนดไว้ล่วงหน้า ในการพัฒนา iOS Delegate เป็นหนึ่งในรูปแบบพื้นฐานของ Cocoa Touch ที่ใช้สำหรับการแจ้งเตือนแบบอะซิงโครนัสโดยไม่มีการเชื่อมโยงโดยตรงระหว่างผู้ส่งและผู้รับ ตาม Apple Documentation (2025) การมอบหมายถูกใช้ใน Foundation และ UIKit สำหรับจัดการเหตุการณ์ของตาราง คำขอเครือข่าย และการจัดการตำแหน่งที่ตั้ง รูปแบบนี้ช่วยให้ส่วนประกอบมีการเชื่อมโยงแบบหลวม ๆ และสามารถนำโค้ดกลับมาใช้ใหม่ได้
ประเด็นสำคัญ
Delegate (ผู้รับมอบหมาย) คือวัตถุที่implementโปรโตคอลเฉพาะและได้รับการแจ้งเตือนเกี่ยวกับเหตุการณ์ของวัตถุอื่น รูปแบบการมอบหมายเป็นทางเลือกแทนการสืบทอด: แทนที่จะสร้างคลาสย่อยเพื่อoverrideเมธอด วัตถุจะมอบหมายการจัดการเหตุการณ์ให้กับวัตถุภายนอก ในiOS การมอบหมายถูกimplementผ่านโปรโตคอล Swift พร้อมเมธอดที่จำเป็นและเป็นตัวเลือก คุณสมบัติ delegate จะถูกประกาศเป็น weak var เสมอเพื่อหลีกเลี่ยงการอ้างอิงแบบวงกลมระหว่างวัตถุ
โปรโตคอล delegate กำหนดสัญญาการโต้ตอบระหว่างวัตถุ เมธอดที่จำเป็น ต้องถูกimplementโดยผู้รับมอบหมาย มิฉะนั้นโค้ดจะไม่คอมไพล์ เมธอดที่เป็นตัวเลือก จะถูกทำเครื่องหมายด้วยแอตทริบิวต์ @objc optional และอนุญาตให้ผู้รับมอบหมายตอบสนองต่อเฉพาะเหตุการณ์ที่เกี่ยวข้อง ชื่อเมธอดเป็นไปตามธรรมเนียม: พารามิเตอร์แรกคือวัตถุผู้ส่ง พารามิเตอร์ที่สองคือข้อมูลเหตุการณ์ ตัวอย่างเช่น tableView(_:didSelectRowAt:) ระบุว่าผู้ส่งคือ UITableView และข้อมูลคือดัชนีของแถวที่เลือก
// โปรโตคอล 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)
}
// คลาสที่ใช้ 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()
}
}
คุณสมบัติ delegate ต้องถูกประกาศเป็น weak var เพื่อป้องกัน retain cycle หากการอ้างอิงเป็นแบบแข็ง ผู้รับมอบหมายและวัตถุที่มอบหมายจะยึดซึ่งกันและกัน และ ARC จะไม่สามารถ freeing หน่วยความจำของพวกมันได้ โปรโตคอล delegate สืบทอด AnyObject (เฉพาะคลาส) ซึ่งอนุญาตให้ใช้ weak ได้ โครงสร้างและenumไม่สามารถเป็นผู้รับมอบหมายได้เนื่องจากความหมายของค่า ทางเลือกสำหรับชนิดค่าคือ callback closures
class ViewController: DownloadManagerDelegate {
let manager = DownloadManager()
override func viewDidLoad() {
super.viewDidLoad()
manager.delegate = self // weak — ไม่มี retain cycle
manager.startDownload(from: url)
}
func downloadManager(_ manager: DownloadManager, didFinishWith data: Data) {
processData(data)
}
func downloadManager(_ manager: DownloadManager, didFailWith error: Error) {
showError(error)
}
}
รูปแบบ Delegate ทำงานแบบหนึ่งต่อหนึ่ง: วัตถุผู้ส่งหนึ่งรายการสามารถมี delegate ได้เพียงรายการเดียวในเวลาใดเวลาหนึ่ง เมื่อเหตุการณ์เกิดขึ้น ผู้ส่งจะตรวจสอบว่า delegate ถูกตั้งไว้หรือไม่ และเรียกเมธอดโปรโตคอลที่เกี่ยวข้อง ข้อได้เปรียบเหนือการเรียกโดยตรงคือผู้ส่งไม่รู้จักชนิดของ delegate เพียงแต่ว่ามันสอดคล้องกับโปรโตคอล ซึ่งเป็นไปตามหลักการการกลับด้านการพึ่งพา (DIP) จาก SOLID
ผู้รับมอบหมายถูกกำหนดผ่านการกำหนด: someObject.delegate = self เมื่อผู้รับมอบหมายถูกดีโลเคท คุณสมบัติจะกลายเป็น nil โดยอัตโนมัติเนื่องจากความหมายแบบ weak ก่อนที่จะเรียกเมธอดของผู้รับมอบหมาย ผู้รับมอบหมายจะถูกตรวจสอบผ่านการเชื่อมโยงแบบตัวเลือก: delegate?.method() ถ้า delegate เป็น nil การเรียกจะถูกข้ามไปโดยไม่พัง สำหรับเมธอดที่เป็นตัวเลือกของโปรโตคอล จะใช้การตรวจสอบเพิ่มเติม: delegate?.responds(to: #selector(...)) แม้ว่าใน Swift การตรวจสอบนี้มักจะเป็นแบบโดยนัยผ่านการประกาศเมธอดที่เป็นตัวเลือก
ในสภาพแวดล้อมแบบหลายเธรด delegate ถูกใช้สำหรับการส่งคืนผลลัพธ์แบบอะซิงโครนัส URLSession ให้ URLSessionDelegate พร้อมเมธอดที่ถูกเรียกเมื่อได้รับข้อมูล หมดเวลา หรือข้อผิดพลาดการรับรองความถูกต้อง เมธอดของ delegate ถูกดำเนินการในคิวพื้นหลังของ URLSession ดังนั้นจึงจำเป็นต้องส่งไปยังคิวหลักสำหรับการอัปเดต UI delegate แบบอะซิงโครนัสไม่บล็อกเธรดที่เรียก ทำให้งานอื่น ๆ สามารถดำเนินต่อไปได้
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 และ Callback แก้ปัญหาเดียวกัน — การแจ้งเตือนแบบอะซิงโครนัส — แต่ในวิธีที่ต่างกัน Delegate ใช้โปรโตคอลพร้อมเมธอดที่มีชื่อ Callback ใช้ closure พร้อมการจับบริบท ตัวเลือกขึ้นอยู่กับจำนวนเหตุการณ์ ความซับซ้อนของลายเซ็น และความชอบทางสถาปัตยกรรม Apple แนะนำ Delegate สำหรับ API ที่มีหลายเหตุการณ์ (UITableView — 20+ เมธอด) และ Callback สำหรับการเสร็จสิ้นครั้งเดียว
Delegate เหนือกว่าเมื่อต้องจัดการกับหลายเหตุการณ์ที่แตกต่างจากแหล่งเดียว ตัวอย่างเช่น CLLocationManager แจ้งผู้รับมอบหมายเกี่ยวกับการเปลี่ยนแปลงตำแหน่ง ข้อผิดพลาดสิทธิ์ การเข้า/ออกจากรั้วภูมิศาสตร์ และการเปลี่ยนแปลงสถานะบริการ แต่ละเหตุการณ์เป็นเมธอดโปรโตคอลแยกต่างหากที่มีชื่อชัดเจนและพารามิเตอร์ที่ระบุชนิด Delegate ยังสะดวกสำหรับการกำหนดค่าพฤติกรรม (เมธอด should, will, did)
Callback ง่ายกว่าสำหรับคำขอครั้งเดียวที่มีผลลัพธ์เดียว Completion handler ใน URLSession.dataTask ใช้หนึ่งบรรทัดที่จุดเรียกเทียบกับอย่างน้อยสามเมธอดของโปรโตคอล Callback ยังเป็นธรรมชาติมากกว่าสำหรับลูกโซ่ฟังก์ชัน (map, flatMap, async/await) อย่างไรก็ตาม ด้วยการซ้อนมากกว่า 2-3 ระดับ Callback จะกลายเป็น Callback Hell ในขณะที่ delegate ยังคงราบเรียบเสมอ
iOS SDK ประกอบด้วยโปรโตคอล delegate ในตัวหลายสิบรายการสำหรับระบบย่อยต่าง ๆ แต่ละรายการถูกออกแบบสำหรับสถานการณ์การโต้ตอบเฉพาะ ตาม Apple Documentation (2025) delegate ที่ใช้มากที่สุดคือ UITableViewDelegate, UITextFieldDelegate, CLLocationManagerDelegate, URLSessionDelegate และ UNUserNotificationCenterDelegate โปรโตคอลเหล่านี้มี 3 ถึง 30 เมธอดพร้อมระดับความจำเป็นที่แตกต่างกัน
UITableViewDelegate จัดการลักษณะที่ปรากฏและพฤติกรรมของเซลล์ตาราง ประกอบด้วยเมธอดสำหรับจัดการการเลือกแถว การกำหนดความสูงของเซลล์ มุมมองส่วนหัว/ท้ายแบบกำหนดเอง และการดำเนินการปัด ทุกเมธอดของโปรโตคอลเป็นตัวเลือก ทำให้สามารถimplementเฉพาะฟังก์ชันที่ต้องการได้ หากไม่มี delegate ตารางจะทำงานด้วยการตั้งค่าเริ่มต้น ในอดีต delegate ถูกรวมกับ UITableViewDataSource
URLSessionDelegate ให้การควบคุมโดยละเอียดเหนือคำขอ HTTP เมธอดของ delegate จะถูกเรียกเมื่อได้รับการตอบสนองจากเซิร์ฟเวอร์ ข้อมูลมาถึง หรือดาวน์โหลดเสร็จสมบูรณ์ โปรโตคอลย่อยเฉพาะทาง URLSessionTaskDelegate และ URLSessionDataDelegate ขยายฟังก์ชันพื้นฐานสำหรับชนิดงานเฉพาะ จำเป็นต้องใช้ delegate เพื่อรองรับการดาวน์โหลดพื้นหลัง ใบรับรอง SSL และการจัดการการเปลี่ยนเส้นทางแบบกำหนดเอง
| Delegate | เมธอด | วัตถุประสงค์ |
|---|---|---|
| UITableViewDelegate | 25 | ลักษณะที่ปรากฏและการโต้ตอบของตาราง |
| UITextFieldDelegate | 8 | การจัดการการป้อนข้อความและแป้นพิมพ์ |
| CLLocationManagerDelegate | 12 | การอัปเดตตำแหน่งและรั้วภูมิศาสตร์ |
| URLSessionDelegate | 6 | การจัดการเซสชัน HTTP และใบรับรอง |
| UNUserNotificationCenterDelegate | 4 | การจัดการการแจ้งเตือน Push ในเบื้องหน้า |
การจัดการหน่วยความจำเป็นลักษณะสำคัญของการทำงานกับ delegate ใน iOS ARC (การนับการอ้างอิงอัตโนมัติ) จัดการหน่วยความจำโดยอัตโนมัติ แต่เฉพาะกับการใช้การอ้างอิงแบบ weak/unowned อย่างถูกต้อง การละเมิดกฎนำไปสู่การรั่วไหลของหน่วยความจำหรือการดีโลเคทก่อนกำหนด delegate ที่ประกาศเป็น strong จะสร้าง retain cycle หากเจ้าของ delegate ยังคงการอ้างอิงไปยังวัตถุที่มอบหมาย
Retain cycle เกิดขึ้นเมื่อวัตถุ A (เจ้าของ) ตั้งตัวเองเป็น delegate ของวัตถุ B และ B เก็บการอ้างอิงแบบแข็งไปยัง delegate ตัวอย่าง: ViewController สร้าง URLSession ตั้งตัวเองเป็น delegate ของเซสชัน แต่ URLSession โดยค่าเริ่มต้นเก็บการอ้างอิงแบบแข็งไปยัง delegate หากไม่ได้ระบุ delegateQueue วิธีแก้คือตรวจสอบเอกสาร API สำหรับชนิดการอ้างอิง delegate (weak หรือ strong) เสมอ และตั้ง delegate เป็น nil อย่างชัดเจนใน deinit
class SafeViewController: UIViewController {
private var session: URLSession?
private var service: NetworkService?
override func viewDidLoad() {
super.viewDidLoad()
service = NetworkService()
service?.delegate = self
}
deinit {
// ตั้ง delegate เป็น nil ใน deinit — best practice
service?.delegate = nil
session?.invalidateAndCancel()
}
}
// URLSession พร้อม weak delegate ผ่าน 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
)
}
}
ก่อนเรียกเมธอดของ delegate คุณต้องตรวจสอบว่า delegate มีอยู่ (ไม่ใช่ nil) และimplementเมธอดที่ถูกเรียก สำหรับเมธอดที่จำเป็นของโปรโตคอล ไม่จำเป็นต้องตรวจสอบ — คอมไพเลอร์รับประกันการimplement สำหรับ เมธอดที่เป็นตัวเลือก ให้ใช้ respond(to:) หรือการเชื่อมโยงแบบตัวเลือก ถ้า delegate ถูกดีโลเคท การอ้างอิงแบบ weak จะกลายเป็น nil โดยอัตโนมัติ และการเรียก delegate จะถูกข้ามไป ซึ่งเป็นพฤติกรรมที่ปลอดภัยที่ไม่ต้องการการจัดการเพิ่มเติม
นักพัฒนามักทำผิดพลาดเมื่อทำงานกับรูปแบบ Delegate โดยเฉพาะในระยะแรกของการเรียนรู้ iOS ที่พบบ่อยที่สุด ได้แก่: retain cycle จาก delegate แบบ strong, ลืมเรียก delegate?.method(), ลายเซ็นเมธอดโปรโตคอลไม่ถูกต้อง, การตั้ง delegate หลังจากเริ่มดำเนินการ และการชนกันของหลายเธรด มาดูแต่ละข้อผิดพลาดและวิธีป้องกัน
ข้อผิดพลาดที่สำคัญที่สุดคือการประกาศคุณสมบัติ delegate เป็น strong var แทน weak var ซึ่งสร้าง retain cycle ที่ทั้ง delegate และวัตถุที่มอบหมายไม่สามารถถูก freeing ได้ ผลกระทบ: หน่วยความจำรั่วไหล แอปช้าลง และบักที่ซ่อนอยู่ วิธีแก้: ใช้ weak var สำหรับ delegate เสมอ และให้โปรโตคอลสืบทอดจาก AnyObject เพื่อป้องกันการใช้ชนิดค่าเป็น delegate
ถ้า delegate ถูกตั้งหลังจากเรียกเมธอดแบบอะซิงโครนัส เหตุการณ์แรกอาจสูญหาย ตัวอย่าง: การเรียก startDownload() ก่อนกำหนด manager.delegate = self ส่งผลให้สูญเสีย callback การเสร็จสิ้นหากการดาวน์โหลดดำเนินการแบบซิงโครนัสหรือเร็วมาก วิธีแก้: ตั้ง delegate ก่อนเรียกเมธอดแบบอะซิงโครนัส และบันทึกลำดับการเริ่มต้นในความคิดเห็นของโปรโตคอล
คำถามที่พบบ่อย
Weak ป้องกัน retain cycle ระหว่าง delegate และวัตถุที่มอบหมาย หากการอ้างอิงเป็นแบบแข็ง วัตถุจะยึดซึ่งกันและกัน และ ARC จะไม่สามารถ freeing พวกมันได้ การอ้างอิงแบบ weak จะกลายเป็น nil โดยอัตโนมัติเมื่อ delegate ถูกดีโลเคท นี่เป็นแนวปฏิบัติมาตรฐานของ Cocoa Touch ตั้งแต่การถือกำเนิดของ Objective-C และถูกเก็บไว้ใน Swift เพื่อความเข้ากันได้ย้อนหลัง
Delegate จัดการเหตุการณ์และพฤติกรรม (ความสูงของเซลล์ การตอบสนองต่อการแตะ) DataSource ให้ข้อมูลสำหรับการแสดงผล (จำนวนแถว เซลล์) ผู้รับมอบหมายตอบคำถาม “อย่างไร?” dataSource ตอบคำถาม “อะไร?” ในiOS ทั้งคู่ถูกimplementผ่านโปรโตคอล มักอยู่ในคอนโทรลเลอร์เดียวกัน แต่แยกแนวคิดกัน
ไม่ได้ ถ้าโปรโตคอลสืบทอดจาก AnyObject (โปรโตคอลคลาส) การอ้างอิงแบบ weak ใช้ได้เฉพาะกับชนิดอ้างอิง (คลาส) เท่านั้น สำหรับชนิดค่า (struct, enum) ให้ใช้ callback closures หรือคลาส wrapper แยกต่างหาก ถ้าคุณควบคุมโปรโตคอล คุณสามารถหลีกเลี่ยงการสืบทอด AnyObject แต่แล้ว weak จะถูกห้าม — เลือกอย่างมีสติระหว่าง delegate แบบ weak และ delegate แบบ struct
responds(to:) เป็นเมธอดของ NSObjectProtocol ที่ตรวจสอบว่าวัตถุimplementซีเล็กเตอร์ที่ระบุหรือไม่ ใช้สำหรับตรวจสอบเมธอดที่เป็นตัวเลือก @objc ของโปรโตคอลก่อนเรียก หากไม่มีการตรวจสอบนี้ การเรียกเมธอดที่เป็นตัวเลือกที่ยังไม่ได้implementจะทำให้เกิด NSInvalidArgumentException ในSwift สำหรับโปรโตคอลที่มี @objc optional การตรวจสอบสามารถเป็นแบบโดยนัยผ่าน optional binding
ไม่ delegate เป็นรูปแบบการมอบหมาย ไม่ใช่ singleton แตกต่างจาก singleton ผู้รับมอบหมายสามารถถูกแทนที่ได้ในruntime และมีอยู่ในอินสแตนซ์เดียวสำหรับแต่ละวัตถุที่มอบหมาย วัตถุหนึ่งสามารถเป็น delegate สำหรับผู้ส่งหลายราย Singleton เป็นรูปแบบการสร้างที่รับประกันอินสแตนซ์คลาสเดียว ซึ่งไม่เกี่ยวข้องกับการมอบหมาย
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม