Delegate هو نمط تصميم يقوم فيه كائن بتفويض تنفيذ المهام إلى كائن آخر عبر بروتوكول بأساليب محددة مسبقاً. في تطوير iOS، يعتبر Delegate أحد الأنماط الأساسية لـ Cocoa Touch، المستخدم للإشعار غير المتزامن دون اقتران مباشر بين المرسل والمستقبل. وفقاً لـ Apple Documentation (2025)، يتم استخدام التفويض في Foundation وUIKit لمعالجة أحداث الجداول وطلبات الشبكة وإدارة الموقع. يضمن النمط اقتراناً ضعيفاً للمكونات وإعادة استخدام الكود.
الملامح الرئيسية
Delegate (مفوّض) هو كائن يقوم بتنفيذ بروتوكول معين ويتلقى إشعارات حول أحداث كائن آخر. نمط التفويض هو بديل للوراثة: بدلاً من إنشاء فئة فرعية لإعادة تعريف الأساليب، يقوم الكائن بتفويض معالجة الأحداث إلى كائن خارجي. في iOS، يتم تنفيذ التفويض من خلال بروتوكولات Swift بأساليب إلزامية واختيارية. يتم دائماً تعريف خاصية delegate كـ weak var لتجنب المراجع الدائرية بين الكائنات.
يحدد بروتوكول delegate عقد التفاعل بين الكائنات. الأساليب الإلزامية يجب أن ينفذها المفوض، وإلا فلن يتم تجميع الكود. الأساليب الاختيارية يتم وضع علامة عليها بـ @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 cycles. إذا كان المرجع قوياً، فإن المفوض والكائن المفوض سيمسكان ببعضهما البعض، ولن يتمكن ARC من تحرير ذاكرتهما. ترث بروتوكولات delegate من AnyObject (فئات فقط)، مما يسمح باستخدام weak. لا يمكن للهياكل والتعدادات أن تكون مفوضين بسبب دلالات القيمة. البديل لأنواع القيم هو 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 على أساس واحد لواحد: يمكن لكائن مرسل واحد أن يكون لديه مفوض واحد فقط في وقت معين. عند وقوع حدث، يتحقق المرسل مما إذا كان المفوض معيناً ويستدعي طريقة البروتوكول المقابلة. الميزة على الاستدعاءات المباشرة هي أن المرسل لا يعرف نوع المفوض، فقط أنه يتوافق مع البروتوكول. وهذا يتوافق مع مبدأ عكس التبعية (DIP) من SOLID.
يتم تعيين المفوض من خلال الإسناد: someObject.delegate = self. عند إلغاء تخصيص المفوض، تصبح الخاصية تلقائياً nil بسبب دلالات weak. قبل استدعاء طريقة المفوض، يتم التحقق من المفوض عبر optional chaining: delegate?.method(). إذا كان المفوض nil، يتم تجاهل الاستدعاء دون تعطل. للأساليب الاختيارية للبروتوكول، يتم استخدام تحقق إضافي: delegate?.responds(to: #selector(...))، على الرغم من أن هذا التحقق في Swift يكون ضمنياً عادةً من خلال إعلان الأسلوب الاختياري.
في بيئة متعددة الخيوط، يتم استخدام المفوض لإرجاع النتائج بشكل غير متزامن. URLSession يوفر URLSessionDelegate بأساليب يتم استدعاؤها عند استلام البيانات أو انتهاء المهلة أو خطأ المصادقة. يتم تنفيذ أساليب المفوض في قائمة الخلفية لـ URLSession، لذلك يلزم التوجيه إلى القائمة الرئيسية لتحديثات واجهة المستخدم. المفوض غير المتزامن لا يحجب خيط الاستدعاء، مما يسمح بمواصلة المهام الأخرى.
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 إغلاقاً مع التقاط السياق. يعتمد الاختيار على عدد الأحداث، وتعقيد التوقيعات، والتفضيلات المعمارية. توصي Apple باستخدام Delegate لواجهات برمجة التطبيقات ذات الأحداث المتعددة (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 على عشرات بروتوكولات المفوضين المدمجة لأنظمة فرعية مختلفة. كل منها مصمم لسيناريو تفاعل محدد. وفقاً لـ Apple Documentation (2025)، أكثر المفوضين استخداماً هم UITableViewDelegate وUITextFieldDelegate وCLLocationManagerDelegate وURLSessionDelegate وUNUserNotificationCenterDelegate. تحتوي هذه البروتوكولات على 3 إلى 30 أسلوباً بمستويات مختلفة من الإلزام.
UITableViewDelegate يدير مظهر وسلوك خلايا الجدول. يحتوي على أساليب لمعالجة تحديد الصفوف وتكوين ارتفاع الخلايا وعروض الرأس/التذييل المخصصة وإجراءات السحب. جميع أساليب البروتوكول اختيارية، مما يسمح بتنفيذ الوظائف المطلوبة فقط. بدون مفوض، يعمل الجدول بالإعدادات الافتراضية. تاريخياً، كان المفوض مقترناً بـ UITableViewDataSource.
URLSessionDelegate يوفر تحكماً مفصلاً في طلبات HTTP. يتم استدعاء أساليب المفوض عند استلام استجابة الخادم أو البيانات أو اكتمال التنزيل. تعمل البروتوكولات الفرعية المتخصصة URLSessionTaskDelegate وURLSessionDataDelegate على توسيع الوظائف الأساسية لأنواع مهام محددة. يتطلب المفوض لدعم التنزيلات في الخلفية وشهادات SSL ومعالجة إعادة التوجيه المخصصة.
| Delegate | الأساليب | الغرض |
|---|---|---|
| UITableViewDelegate | 25 | مظهر الجدول والتفاعل |
| UITextFieldDelegate | 8 | معالجة إدخال النص ولوحة المفاتيح |
| CLLocationManagerDelegate | 12 | تحديثات الموقع والمناطق الجغرافية |
| URLSessionDelegate | 6 | إدارة جلسة HTTP والشهادات |
| UNUserNotificationCenterDelegate | 4 | معالجة إشعارات الدفع في المقدمة |
إدارة الذاكرة هي جانب حاسم في العمل مع delegate في iOS. ARC (العد التلقائي للمراجع) يدير الذاكرة تلقائياً، ولكن فقط مع الاستخدام الصحيح للمراجع weak/unowned. انتهاك القواعد يؤدي إلى تسرب الذاكرة أو الإلغاء المبكر للتخصيص. المفوض المعلن كـ strong ينشئ retain cycle إذا كان مالك المفوض يحمل أيضاً مرجعاً للكائن المفوض.
يحدث retain cycle عندما يعين الكائن A (المالك) نفسه كمفوض للكائن B، ويحتفظ B بمرجع قوي للمفوض. مثال: ViewController ينشئ URLSession، ويعين نفسه كمفوض للجلسة، لكن URLSession يحتفظ افتراضياً بمرجع قوي للمفوض إذا لم يتم تحديد delegateQueue. الحل هو دائماً التحقق من وثائق API لنوع مرجع المفوض (weak أو strong) وتعيين المفوض صراحةً إلى nil في deinit.
class SafeViewController: UIViewController {
private var session: URLSession?
private var service: NetworkService?
override func viewDidLoad() {
super.viewDidLoad()
service = NetworkService()
service?.delegate = self
}
deinit {
// تعيين المفوض إلى 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
)
}
}
قبل استدعاء طريقة المفوض، يجب التحقق من أن المفوض موجود (ليس nil) وينفذ الطريقة المستدعاة. للأساليب الإلزامية للبروتوكول، لا يلزم التحقق — المترجم يضمن التنفيذ. للـ أساليب الاختيارية، استخدم respond(to:) أو optional chaining. إذا تم إلغاء تخصيص المفوض، يصبح المرجع weak تلقائياً nil، ويتم تجاهل استدعاء المفوض. هذا سلوك آمن لا يتطلب معالجة إضافية.
غالباً ما يرتكب المطورون أخطاء عند العمل مع نمط Delegate، خاصة في المراحل المبكرة من تعلم iOS. الأكثر شيوعاً تشمل: retain cycle بسبب delegate من نوع strong، نسيان استدعاء delegate?.method()، توقيع غير صحيح لأساليب البروتوكول، تعيين المفوض بعد بدء العملية، وتصادمات تعدد الخيوط. دعنا ننظر إلى كل خطأ وكيفية منعه.
الخطأ الأكثر خطورة هو تعريف خاصية المفوض كـ strong var بدلاً من weak var. هذا ينشئ retain cycle حيث لا يمكن تحرير لا المفوض ولا الكائن المفوض. العواقب: تسرب الذاكرة، بطء التطبيق، وأخطاء خفية. الحل: استخدم دائماً weak var للمفوض، واجعل البروتوكول يرث من AnyObject لمنع استخدام أنواع القيم كمفوضين.
إذا تم تعيين المفوض بعد استدعاء طريقة غير متزامنة، فقد يتم فقدان الأحداث الأولى. مثال: استدعاء startDownload() قبل تعيين manager.delegate = self يؤدي إلى فقدان callback الإكمال إذا تم التنزيل بشكل متزامن أو بسرعة كبيرة. الحل: قم بتعيين المفوض قبل استدعاء الطريقة غير المتزامنة وقم بتوثيق ترتيب التهيئة في تعليقات البروتوكول.
الأسئلة الشائعة
Weak يمنع retain cycle بين المفوض والكائن المفوض. إذا كان المرجع قوياً، فسيتمسك الكائنان ببعضهما البعض، ولن يتمكن ARC من تحريرهما. يصبح المرجع weak تلقائياً nil عند إلغاء تخصيص المفوض. هذه ممارسة قياسية لـ Cocoa Touch منذ ظهور Objective-C وتم الاحتفاظ بها في Swift للتوافق مع الإصدارات السابقة.
Delegate يعالج الأحداث ويدير السلوك (ارتفاع الخلايا، الاستجابة للنقرات). DataSource يوفر البيانات للعرض (عدد الصفوف، الخلايا). يجيب المفوض على سؤال «كيف؟»، ويجيب dataSource على سؤال «ماذا؟». في iOS، يتم تنفيذ كلاهما عبر بروتوكولات، غالباً في نفس وحدة التحكم، لكنهما منفصلان من الناحية المفاهيمية.
لا، إذا كان البروتوكول يرث من AnyObject (بروتوكول فئة). المراجع weak متاحة فقط لأنواع المرجع (الفئات). لأنواع القيم (struct، enum)، استخدم callback closures أو فئة غلاف منفصلة. إذا كنت تتحكم في البروتوكول، يمكنك تجنب وراثة AnyObject، ولكن عندها يكون weak محظوراً — اختر بين مفوض weak ومفوض struct بوعي.
responds(to:) هي طريقة من NSObjectProtocol تتحقق مما إذا كان الكائن ينفذ المحدد المحدد. تُستخدم للتحقق من الأساليب الاختيارية @objc للبروتوكول قبل استدعائها. بدون هذا التحقق، استدعاء أسلوب اختياري غير مُنفَّذ سيؤدي إلى NSInvalidArgumentException. في Swift، للبروتوكولات ذات @objc optional، يمكن أن يكون التحقق ضمنياً من خلال optional binding.
لا، delegate هو نمط تفويض وليس singleton. على عكس singleton، يمكن استبدال المفوض في وقت التشغيل ويوجد في نسخة واحدة لكل كائن مفوض. يمكن لكائن واحد أن يكون مفوضاً لعدة مرسلين. Singleton هو نمط إنشائي يضمن نسخة فئة واحدة، لا علاقة له بالتفويض.
الخلاصة
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.
اقرأ أيضًا