Delegate هو نمط تصميم حيث يفوض كائن تنفيذ مهمة إلى كائن آخر. في iOS، يتم تنفيذ النمط من خلال بروتوكولات Swift و @protocol Objective-C. التفويض هو أحد الأنماط الأساسية لـ Cocoa Touch، المستخدم في UITableViewDelegate و UITextFieldDelegate ومئات من واجهات Apple الأخرى. وفقًا لوثائق مطوري Apple (2025)، حوالي 70% من فئات النظام UIKit تستخدم المفوضين لتخصيص السلوك دون وراثة.
النقاط الرئيسية
Delegate هو نمط تصميم سلوكي يسمح لكائن بتفويض جزء من مسؤولياته إلى كائن آخر. على عكس الوراثة، حيث تقوم الفئة الفرعية بتجاوز أساليب الفئة الأم، يستخدم المفوض التكوين: الكائن المالك يحتفظ بمرجع للمفوض ويستدعي أساليبه في نقاط محددة.
يحدد المفوض بروتوكولًا — مجموعة من الأساليب التي يمكن للمفوض تنفيذها. تنقسم الأساليب إلى إلزامية واختيارية. في Swift، يتم تمييز الأساليب الاختيارية للبروتوكول بالكلمة الأساسية @objc optional.
الكائن A (المالك) يحتوي على خاصية delegate — مرجع ضعيف للكائن B (المفوض). عند حدوث حدث، يتحقق A مما إذا كان B ينفذ طريقة البروتوكول المقابلة ويستدعيها. المرجع الضعيف إلزامي: بدونه، لا يمكن تحرير المفوض من الذاكرة لأن المالك يحتفظ به بمرجع قوي.
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)
// تحميل غير متزامن
}
}تحدد فئة DataLoader بروتوكول LoaderDelegate وتستدعي أساليب المفوض في النقاط الرئيسية لدورة التحميل. يضمن AnyObject أن البروتوكول يمكن تنفيذه فقط بواسطة الفئات — وهذا ضروري للمرجع الضعيف.
تنفيذ المفوض في Swift يشمل ثلاث خطوات: إعلان البروتوكول، إنشاء خاصية delegate ضعيفة في المالك، وتنفيذ البروتوكول في فئة المفوض. لننظر إلى UITextField مخصص مع التحقق كمثال.
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("فشل التحقق: الإدخال قصير جدًا")
}
}LoginViewController ينفذ بروتوكول ValidatorDelegate ويعين نفسه كمفوض لـ textField. عند كل تغيير في النص، يستدعي ValidatedTextField validate(_:)، وإذا فشل التحقق — validatorDidFail(_:). يعمل المتحكم كوسيط بين View ومنطق التحقق.
الاختيار بين المفوض والإشعارات و closures يعتمد على عدد المستقبلين واقتران المكونات. كل آلية تحل مشكلة التواصل بين الكائنات، ولكن بمقايضات مختلفة.
| الخاصية | Delegate | NotificationCenter | Closure |
|---|---|---|---|
| نوع الاتصال | 1:1 | 1:N | 1:1 |
| الاقتران | ضعيف (عبر بروتوكول) | ضعيف جدًا (مفتاح نصي) | متوسط (التقاط السياق) |
| أمان الأنواع | كامل | لا يوجد (Any?) | كامل |
| خطر دورة الاحتفاظ | لا (weak) | لا | نعم (التقاط self) |
| متى تستخدم | استدعاءات معقدة بأساليب متعددة | أحداث تهم العديد | مغلقات بسيطة بـ 1-2 استدعاء |
Delegate مثالي عندما تحتاج إلى تمرير سلسلة من الأحداث ذات الصلة إلى مستلم واحد. NotificationCenter أفضل للإشعارات الواسعة. Closure للعمليات غير المتزامنة البسيطة، مثل معالجات الإكمال في URLSession.
Objective-C يستخدم @protocol و @optional لإعلان المفوضين. على عكس Swift، جميع أساليب البروتوكول اختيارية افتراضيًا. الفرق الرئيسي هو استدعاء respondsToSelector: قبل إرسال رسالة إلى المفوض، لأن الطريقة قد لا تكون منفذة.
@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];
}
// تحميل صورة غير متزامن
}
@endالفرق الرئيسي في Objective-C: قبل استدعاء طريقة اختيارية، من الضروري التحقق بـ respondsToSelector:. في Swift، الأساليب الاختيارية للبروتوكول تلغي هذا التحقق — السلسلة الاختيارية (?.) تتعامل تلقائيًا مع غياب التنفيذ.
أخطاء استخدام المفوضين تؤدي إلى تسريبات الذاكرة، وتعطل التطبيق، وأخطاء غير واضحة. لننظر إلى خمس مشاكل شائعة.
دورة الاحتفاظ هي الخطأ الأكثر شيوعًا. إذا تم إعلان خاصية delegate كـ strong، وكان المفوض بدوره يمتلك الكائن المالك، تتشكل دورة احتفاظ. لن يتم تحرير كلا الكائنين من الذاكرة أبدًا. الحل: دائمًا أعلن المفوض كـ weak var في Swift أو @property (weak) في Objective-C.
إذا نجا الكائن المالك من المفوض وبقي المرجع، فإن استدعاء طريقة المفوض سيؤدي إلى EXC_BAD_ACCESS. المرجع الضعيف يحل هذه المشكلة تلقائيًا: بعد تحرير المفوض، تصبح الخاصية nil. ومع ذلك، في السيناريوهات متعددة الخيوط، يجب التحقق إضافيًا من المفوض في الخيط الرئيسي.
بروتوكول بأكثر من 20 طريقة ينتهك مبدأ فصل الواجهات (ISP). UITableViewDelegate يحتوي على حوالي 30 طريقة اختيارية — وهذا استثناء تاريخي. في بروتوكولاتك الخاصة، من الأفضل تقسيم المسؤولية إلى عدة بروتوكولات أصغر، لكل منها دوره الخاص.
واجهات Apple النظامية تستخدم نمط Delegate بنشاط. لننظر إلى ثلاثة أمثلة رئيسية من UIKit تظهر في كل تطبيق iOS.
| API | البروتوكول | الأساليب الرئيسية |
|---|---|---|
| UITableView | UITableViewDelegate | didSelectRowAt, heightForRowAt, willDisplay |
| UITextField | UITextFieldDelegate | shouldChangeCharactersIn, didBeginEditing, shouldReturn |
| URLSession | URLSessionDelegate | didReceiveChallenge, didCompleteWithError, didBecomeInvalidWithError |
كل من هذه البروتوكولات تنفذ جوانب مختلفة من السلوك: UITableViewDelegate يدير المظهر والاستجابة للمس، UITextFieldDelegate يتحكم في إدخال النص، URLSessionDelegate يعالج أحداث الشبكة. هذا يوضح مرونة النمط: يمكن تكييف المفوض لأي مجال من المسؤولية.
الأسئلة الشائعة
Delegate يدير السلوك والمظهر (ارتفاع الخلية، الاستجابة للمس). DataSource يوفر البيانات (عدد الصفوف، محتويات الخلايا). في UITableViewDelegate و UITableViewDataSource — هذان بروتوكولان منفصلان يقسمان المسؤولية بين العرض والبيانات.
المرجع الضعيف يمنع دورات الاحتفاظ. المالك (مثل UITableView) يحتفظ بمرجع للمفوض فقط كـ weak. إذا كان المفوض (UIViewController) يمتلك الجدول، فإن المرجع القوي للمفوض سيخلق دورة: ViewController → UITableView → Delegate (ViewController). Weak يكسر هذه الدورة.
في SwiftUI، يتم استخدام نمط Delegate بشكل أقل — يتم استبداله بـ @Binding و @State و closures. ومع ذلك، لا يزال المفوض يُستخدم لتكامل UIKit عبر UIViewRepresentable. على سبيل المثال، MKMapViewDelegate و WKUIDelegate يظلان ذو صلة عند تغليف مكونات UIKit في SwiftUI.
@objc optional يسمح بإعلان أساليب اختيارية في بروتوكول Swift. هذه آلية توافق مع بيئة Objective-C. بدون @objc، جميع أساليب بروتوكول Swift إلزامية افتراضيًا. يُستخدم Optional في بروتوكولات UIKit حيث يمكن للمفوض تنفيذ فقط الأساليب التي يحتاجها.
يمكن أن يمتلك كائن واحد مفوضًا واحدًا فقط لكل خاصية delegate. إذا كنت بحاجة إلى إعلام كائنات متعددة، استخدم مفوضًا متعددًا (multicast delegate)، أو مصفوفة من المفوضين، أو NotificationCenter. نمط Delegate مصمم أصلاً كعلاقة 1:1.
الملخص
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.