Delegate: ما هو، نمط التفويض وكيف يعمل في iOS

المؤلف: IT Sectr نُشر: 2026-03-17 وقت القراءة: 10 دق

Delegate هو نمط تصميم يقوم فيه كائن بتفويض تنفيذ المهام إلى كائن آخر عبر بروتوكول بأساليب محددة مسبقاً. في تطوير iOS، يعتبر Delegate أحد الأنماط الأساسية لـ Cocoa Touch، المستخدم للإشعار غير المتزامن دون اقتران مباشر بين المرسل والمستقبل. وفقاً لـ Apple Documentation (2025)، يتم استخدام التفويض في Foundation وUIKit لمعالجة أحداث الجداول وطلبات الشبكة وإدارة الموقع. يضمن النمط اقتراناً ضعيفاً للمكونات وإعادة استخدام الكود.

الملامح الرئيسية

  • Delegate هو نمط حيث يعهد الكائن بمعالجة الأحداث إلى كائن آخر عبر بروتوكول.
  • Protocol في Swift يحدد مجموعة من الأساليب التي يجب أو يمكن للـ delegate تنفيذها.
  • Weak reference إلزامية لخاصية delegate لتجنب retain cycle.
  • @objc optional يسمح بجعل أساليب البروتوكول اختيارية التنفيذ.
  • URLSessionDelegate هو delegate غير متزامن لمعالجة أحداث طلبات الشبكة.

ما هو Delegate؟

Delegate (مفوّض) هو كائن يقوم بتنفيذ بروتوكول معين ويتلقى إشعارات حول أحداث كائن آخر. نمط التفويض هو بديل للوراثة: بدلاً من إنشاء فئة فرعية لإعادة تعريف الأساليب، يقوم الكائن بتفويض معالجة الأحداث إلى كائن خارجي. في iOS، يتم تنفيذ التفويض من خلال بروتوكولات Swift بأساليب إلزامية واختيارية. يتم دائماً تعريف خاصية delegate كـ weak var لتجنب المراجع الدائرية بين الكائنات.

تعريف بروتوكول Delegate

يحدد بروتوكول delegate عقد التفاعل بين الكائنات. الأساليب الإلزامية يجب أن ينفذها المفوض، وإلا فلن يتم تجميع الكود. الأساليب الاختيارية يتم وضع علامة عليها بـ @objc optional وتسمح للمفوض بالاستجابة فقط للأحداث ذات الصلة. تتبع أسماء الأساليب اصطلاحاً: المعامل الأول هو الكائن المرسل، والثاني هو بيانات الحدث. على سبيل المثال، tableView(_:didSelectRowAt:) يشير إلى أن المرسل هو UITableView والبيانات هي فهرس الصف المحدد.

swift
// بروتوكول 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.

swift
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 في iOS؟

يعمل نمط Delegate على أساس واحد لواحد: يمكن لكائن مرسل واحد أن يكون لديه مفوض واحد فقط في وقت معين. عند وقوع حدث، يتحقق المرسل مما إذا كان المفوض معيناً ويستدعي طريقة البروتوكول المقابلة. الميزة على الاستدعاءات المباشرة هي أن المرسل لا يعرف نوع المفوض، فقط أنه يتوافق مع البروتوكول. وهذا يتوافق مع مبدأ عكس التبعية (DIP) من SOLID.

دورة حياة المفوض

يتم تعيين المفوض من خلال الإسناد: someObject.delegate = self. عند إلغاء تخصيص المفوض، تصبح الخاصية تلقائياً nil بسبب دلالات weak. قبل استدعاء طريقة المفوض، يتم التحقق من المفوض عبر optional chaining: delegate?.method(). إذا كان المفوض nil، يتم تجاهل الاستدعاء دون تعطل. للأساليب الاختيارية للبروتوكول، يتم استخدام تحقق إضافي: delegate?.responds(to: #selector(...))، على الرغم من أن هذا التحقق في Swift يكون ضمنياً عادةً من خلال إعلان الأسلوب الاختياري.

الإشعار غير المتزامن عبر Delegate

في بيئة متعددة الخيوط، يتم استخدام المفوض لإرجاع النتائج بشكل غير متزامن. URLSession يوفر URLSessionDelegate بأساليب يتم استدعاؤها عند استلام البيانات أو انتهاء المهلة أو خطأ المصادقة. يتم تنفيذ أساليب المفوض في قائمة الخلفية لـ URLSession، لذلك يلزم التوجيه إلى القائمة الرئيسية لتحديثات واجهة المستخدم. المفوض غير المتزامن لا يحجب خيط الاستدعاء، مما يسمح بمواصلة المهام الأخرى.

swift
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 vs Callback: مقارنة الأساليب

Delegate و Callback يحلان نفس المشكلة — الإشعار غير المتزامن — ولكن بطرق مختلفة. يستخدم Delegate بروتوكولاً بأساليب مسماة، بينما يستخدم Callback إغلاقاً مع التقاط السياق. يعتمد الاختيار على عدد الأحداث، وتعقيد التوقيعات، والتفضيلات المعمارية. توصي Apple باستخدام Delegate لواجهات برمجة التطبيقات ذات الأحداث المتعددة (UITableView — 20+ أسلوباً) و Callback للإكمالات لمرة واحدة.

متى يفوز Delegate

يفضل Delegate عند التعامل مع عدة أحداث مختلفة من مصدر واحد. على سبيل المثال، CLLocationManager يخطر مفوضه بتغييرات الموقع وأخطاء الأذونات والدخول/الخروج من المناطق الجغرافية وتغييرات حالة الخدمة. كل حدث هو أسلوب بروتوكول منفصل باسم واضح ومعاملات محددة النوع. Delegate مناسب أيضاً لتكوين السلوك (أساليب should، will، did).

متى يفوز Callback

Callback أبسط للطلبات لمرة واحدة بنتيجة واحدة. Completion handler في URLSession.dataTask يشغل سطراً واحداً في موقع الاستدعاء مقابل ثلاثة أساليب بروتوكول على الأقل. Callback أيضاً أكثر طبيعية للسلاسل الوظيفية (map، flatMap، async/await). ومع ذلك، مع التداخل لأكثر من 2-3 مستويات، يتحول Callback إلى Callback Hell، بينما يظل Delegate مسطحاً دائماً.

المفوضون المدمجون في iOS SDK

يحتوي iOS SDK على عشرات بروتوكولات المفوضين المدمجة لأنظمة فرعية مختلفة. كل منها مصمم لسيناريو تفاعل محدد. وفقاً لـ Apple Documentation (2025)، أكثر المفوضين استخداماً هم UITableViewDelegate وUITextFieldDelegate وCLLocationManagerDelegate وURLSessionDelegate وUNUserNotificationCenterDelegate. تحتوي هذه البروتوكولات على 3 إلى 30 أسلوباً بمستويات مختلفة من الإلزام.

UITableViewDelegate

UITableViewDelegate يدير مظهر وسلوك خلايا الجدول. يحتوي على أساليب لمعالجة تحديد الصفوف وتكوين ارتفاع الخلايا وعروض الرأس/التذييل المخصصة وإجراءات السحب. جميع أساليب البروتوكول اختيارية، مما يسمح بتنفيذ الوظائف المطلوبة فقط. بدون مفوض، يعمل الجدول بالإعدادات الافتراضية. تاريخياً، كان المفوض مقترناً بـ UITableViewDataSource.

URLSessionDelegate

URLSessionDelegate يوفر تحكماً مفصلاً في طلبات HTTP. يتم استدعاء أساليب المفوض عند استلام استجابة الخادم أو البيانات أو اكتمال التنزيل. تعمل البروتوكولات الفرعية المتخصصة URLSessionTaskDelegate وURLSessionDataDelegate على توسيع الوظائف الأساسية لأنواع مهام محددة. يتطلب المفوض لدعم التنزيلات في الخلفية وشهادات SSL ومعالجة إعادة التوجيه المخصصة.

Delegateالأساليبالغرض
UITableViewDelegate25مظهر الجدول والتفاعل
UITextFieldDelegate8معالجة إدخال النص ولوحة المفاتيح
CLLocationManagerDelegate12تحديثات الموقع والمناطق الجغرافية
URLSessionDelegate6إدارة جلسة HTTP والشهادات
UNUserNotificationCenterDelegate4معالجة إشعارات الدفع في المقدمة

إدارة الذاكرة مع Delegate

إدارة الذاكرة هي جانب حاسم في العمل مع delegate في iOS. ARC (العد التلقائي للمراجع) يدير الذاكرة تلقائياً، ولكن فقط مع الاستخدام الصحيح للمراجع weak/unowned. انتهاك القواعد يؤدي إلى تسرب الذاكرة أو الإلغاء المبكر للتخصيص. المفوض المعلن كـ strong ينشئ retain cycle إذا كان مالك المفوض يحمل أيضاً مرجعاً للكائن المفوض.

Retain cycle عبر Delegate

يحدث retain cycle عندما يعين الكائن A (المالك) نفسه كمفوض للكائن B، ويحتفظ B بمرجع قوي للمفوض. مثال: ViewController ينشئ URLSession، ويعين نفسه كمفوض للجلسة، لكن URLSession يحتفظ افتراضياً بمرجع قوي للمفوض إذا لم يتم تحديد delegateQueue. الحل هو دائماً التحقق من وثائق API لنوع مرجع المفوض (weak أو strong) وتعيين المفوض صراحةً إلى nil في deinit.

swift
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

غالباً ما يرتكب المطورون أخطاء عند العمل مع نمط Delegate، خاصة في المراحل المبكرة من تعلم iOS. الأكثر شيوعاً تشمل: retain cycle بسبب delegate من نوع strong، نسيان استدعاء delegate?.method()، توقيع غير صحيح لأساليب البروتوكول، تعيين المفوض بعد بدء العملية، وتصادمات تعدد الخيوط. دعنا ننظر إلى كل خطأ وكيفية منعه.

مرجع strong بدلاً من weak

الخطأ الأكثر خطورة هو تعريف خاصية المفوض كـ strong var بدلاً من weak var. هذا ينشئ retain cycle حيث لا يمكن تحرير لا المفوض ولا الكائن المفوض. العواقب: تسرب الذاكرة، بطء التطبيق، وأخطاء خفية. الحل: استخدم دائماً weak var للمفوض، واجعل البروتوكول يرث من AnyObject لمنع استخدام أنواع القيم كمفوضين.

تعيين المفوض بعد بدء العملية

إذا تم تعيين المفوض بعد استدعاء طريقة غير متزامنة، فقد يتم فقدان الأحداث الأولى. مثال: استدعاء startDownload() قبل تعيين manager.delegate = self يؤدي إلى فقدان callback الإكمال إذا تم التنزيل بشكل متزامن أو بسرعة كبيرة. الحل: قم بتعيين المفوض قبل استدعاء الطريقة غير المتزامنة وقم بتوثيق ترتيب التهيئة في تعليقات البروتوكول.

الأسئلة الشائعة

لماذا يتم تعريف delegate كـ weak؟

Weak يمنع retain cycle بين المفوض والكائن المفوض. إذا كان المرجع قوياً، فسيتمسك الكائنان ببعضهما البعض، ولن يتمكن ARC من تحريرهما. يصبح المرجع weak تلقائياً nil عند إلغاء تخصيص المفوض. هذه ممارسة قياسية لـ Cocoa Touch منذ ظهور Objective-C وتم الاحتفاظ بها في Swift للتوافق مع الإصدارات السابقة.

ما الفرق بين delegate وdataSource؟

Delegate يعالج الأحداث ويدير السلوك (ارتفاع الخلايا، الاستجابة للنقرات). DataSource يوفر البيانات للعرض (عدد الصفوف، الخلايا). يجيب المفوض على سؤال «كيف؟»، ويجيب dataSource على سؤال «ماذا؟». في iOS، يتم تنفيذ كلاهما عبر بروتوكولات، غالباً في نفس وحدة التحكم، لكنهما منفصلان من الناحية المفاهيمية.

هل يمكن استخدام struct كمفوض؟

لا، إذا كان البروتوكول يرث من AnyObject (بروتوكول فئة). المراجع weak متاحة فقط لأنواع المرجع (الفئات). لأنواع القيم (struct، enum)، استخدم callback closures أو فئة غلاف منفصلة. إذا كنت تتحكم في البروتوكول، يمكنك تجنب وراثة AnyObject، ولكن عندها يكون weak محظوراً — اختر بين مفوض weak ومفوض struct بوعي.

ما هي طريقة responds(to:) ولماذا هي مطلوبة؟

responds(to:) هي طريقة من NSObjectProtocol تتحقق مما إذا كان الكائن ينفذ المحدد المحدد. تُستخدم للتحقق من الأساليب الاختيارية @objc للبروتوكول قبل استدعائها. بدون هذا التحقق، استدعاء أسلوب اختياري غير مُنفَّذ سيؤدي إلى NSInvalidArgumentException. في Swift، للبروتوكولات ذات @objc optional، يمكن أن يكون التحقق ضمنياً من خلال optional binding.

هل Delegate هو singleton أم لا؟

لا، delegate هو نمط تفويض وليس singleton. على عكس singleton، يمكن استبدال المفوض في وقت التشغيل ويوجد في نسخة واحدة لكل كائن مفوض. يمكن لكائن واحد أن يكون مفوضاً لعدة مرسلين. Singleton هو نمط إنشائي يضمن نسخة فئة واحدة، لا علاقة له بالتفويض.

الخلاصة

  • Delegate هو نمط حيث يعهد الكائن بمعالجة الأحداث إلى كائن آخر عبر بروتوكول بأساليب محددة النوع.
  • Weak var إلزامي لخاصية delegate لمنع retain cycles وتسرب الذاكرة.
  • Protocol يحدد أساليب التفويض الإلزامية والاختيارية (@objc optional).
  • iOS SDK يحتوي على 15+ بروتوكول مفوض مدمج: UITableViewDelegate، URLSessionDelegate، CLLocationManagerDelegate.
  • Delegate أفضل من callback عندما يكون هناك 3+ أحداث مختلفة من مصدر واحد (CLLocationManager).
  • المفوض غير المتزامن يُستخدم في URLSession لاستقبال البيانات والتقدم دون حظر الخيط.
  • قم بتعيين المفوض قبل بدء العملية غير المتزامنة واجعله nil في deinit لإدارة آمنة للذاكرة.

سنقوم بتطوير تطبيق جوال جاهز

تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.

مناقشة المشروع

اقرأ أيضًا