Method Swizzling في تطوير iOS وAndroid: المفاهيم الأساسية والتقنيات ومبدأ العمل

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

Method Swizzling هي تقنية وقت تشغيل يتم فيها تبادل تطبيقات اثنين من طرق الفئة أثناء التنفيذ. تتيح تجاوز أو استكمال سلوك طريقة نظام دون إنشاء فئة فرعية أو تعديل الكود المصدري. وجدت هذه التقنية أكبر تطبيق لها في تطوير iOS باستخدام Objective-C، لكن توجد نظائرها في Kotlin/Android عبر الانعكاس. وفقاً لدليل NSHipster بواسطة Mattt، 2024، فإن swizzling من أقوى وأخطر آليات Objective-C Runtime.

الملخص

  • Method Swizzling — تبادل تطبيقات اثنين من طرق Objective-C في وقت التشغيل عبر sel_registerName وmethod_exchangeImplementations.
  • Objective-C Runtime يمكّن swizzling بفضل الإرسال الديناميكي عبر objc_msgSend وجدول الإرسال.
  • Swizzling على Android يتم عبر Java Reflection مع استبدال التنفيذ في ملفات dex أو عبر Gradle Transform API.
  • مخاطر swizzling — تعارض بين المكتبات، عدم التوافق مع تحديثات iOS، تعطل عند تغيير تواقيع الطرق.
  • Swizzling الآمن يتطلب dispatch_once والذرية واستدعاء التنفيذ الأصلي داخل الطريقة المستبدلة.

ما هو Method Swizzling؟

Method Swizzling هي تقنية وقت تشغيل تتبادل تطبيقات اثنين من طرق Objective-C. بعد swizzling، استدعاء originalSelector ينفذ كود swizzledSelector، والعكس صحيح. هذا ممكن بفضل بنية Objective-C Runtime، حيث كل محدد (SEL) مرتبط بتطبيق (IMP) عبر جدول إرسال — جدول يمكن تعديله أثناء التنفيذ.

مصطلح «swizzling» تم تقديمه في مجتمع مطوري Cocoa في أوائل العقد الأول من القرن الحادي والعشرين. اكتسبت التقنية شهرة واسعة بفضل مكتبات مثل: AFNetworking (swizzling UIWebView لتتبع التحميل)، Aspects (إطار AOP قائم على swizzling) وFLEX (أداة تصحيح تقوم بتبديل طرق النظام للتفتيش). اليوم، يُستخدم swizzling ضمنياً في معظم تطبيقات iOS — من خلال مكتبات المراقبة والتحليلات.

خاصية مهمة للswizzling هي العالمية: استبدال التطبيق يحدث على مستوى الفئة، وليس على مستوى المثيل. إذا قامت مكتبة بتبديل طريقة UIViewController.viewDidLoad، فإن ذلك يؤثر على جميع مثيلات UIViewController في التطبيق، بما في ذلك مثيلات النظام. هذه هي قوة swizzling — سطر واحد من الكود يغير سلوك التطبيق بأكمله — والمصدر الرئيسي للأخطاء.

كيف يعمل Method Swizzling في Objective-C

Objective-C Runtime يخزن في كل فئة جدول إرسال — قاموس حيث المفتاح هو SEL (معرف الطريقة) والقيمة هي IMP (مؤشر لدالة التنفيذ). عندما يرسل التطبيق رسالة إلى كائن، يقوم objc_msgSend ببحث خطي في هذا الجدول. يقوم Method Swizzling باستبدال IMP لمحدد SEL بـ IMP لمحدد SEL آخر، معيداً توجيه الاستدعاءات.

objective-c
// تنفيذ آمن لـ method swizzling
@implementation NSObject (SafeSwizzle)

+ (void)swizzleClassMethod:(SEL)original
                  with:(SEL)swizzled {
    Class cls = [self class];
    SEL originalSel = original;
    SEL swizzledSel = swizzled;

    Method originalMethod = class_getInstanceMethod(cls, originalSel);
    Method swizzledMethod = class_getInstanceMethod(cls, swizzledSel);

    method_exchangeImplementations(originalMethod, swizzledMethod);
}

@end

الدالة الرئيسية هي method_exchangeImplementations(Method, Method). تقوم بتبادل IMPs لكائني Method بشكل ذري. بعد الاستدعاء، يتم تعديل جدول إرسال الفئة: استدعاء original ينفذ كود swizzled، واستدعاء swizzled ينفذ كود original. تضيف فئة SafeSwizzle هذه الطريقة إلى جميع NSObject، مما يسمح لأي فئة بإجراء swizzling.

يتطلب التنفيذ الآمن للswizzling استدعاء التنفيذ الأصلي داخل النسخة المستبدلة. وإلا، يُفقد السلوك الأصلي للطريقة بشكل نهائي. النمط الصحيح هو حفظ IMP الأصلي قبل التبادل واستدعائه في الطريقة المستبدلة:

objective-c
// Swizzling مع استدعاء التنفيذ الأصلي
- (void)swizzled_viewDidLoad {
    // 1. استدعاء التنفيذ الأصلي
    [self swizzled_viewDidLoad];

    // 2. منطق إضافي بعد الاستدعاء الأصلي
    NSLog("viewDidLoad تم تنفيذه، swizzling نشط");
}

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [self swizzleClassMethod:@selector(viewDidLoad)
                            with:@selector(swizzled_viewDidLoad)];
    });
}

dispatch_once يضمن أن swizzling يتم تنفيذه مرة واحدة فقط طوال عمر التطبيق. إعادة swizzling لنفس الطريقة سيؤدي إلى تكرار لا نهائي: الطريقة المستبدلة ستستدعي نفسها. +load يتم استدعاؤه عند تحميل الفئة في وقت التشغيل — إنها نقطة آمنة للswizzling يتم تنفيذها قبل كود التطبيق الرئيسي.

تشريح جدول الإرسال

جدول الإرسال لفئة Objective-C هو مصفوفة من بنى method_t تحتوي على SEL وIMP ونوع القيمة المُعادة. method_exchangeImplementations ببساطة تتبادل مؤشري IMP في هذا الجدول. مهم: swizzling يعمل فقط على مستوى الفئة، وليس على مستوى البروتوكول. إذا تم تعريف طريقة في بروتوكول ولكن لم يتم تنفيذها، فإن جدول الإرسال لا يحتوي على إدخال للswizzling.

تأثير swizzling على الأداء

تكلفة swizzling ضئيلة — تبادل مؤشري IMP في جدول الإرسال يستغرق بضع نانوثوان. بعد swizzling، لا تبطئ استدعاءات الطريقة: objc_msgSend يجد IMP في نفس وقت O(1) كما قبل swizzling. العملية الإضافية الوحيدة هي التحقق من ذاكرة التخزين المؤقت للطرق عند أول استدعاء بعد التبادل. وفقاً لبيانات فريق أداء Apple، لا يؤثر swizzling على أداء التطبيق.

استخدامات Method Swizzling في iOS

Method Swizzling يُستخدم في ثلاثة سيناريوهات رئيسية: المراقبة والتحليلات (تتبع viewDidLoad وviewDidAppear للإرسال التلقائي للأحداث)، اعتراض AOP (تسجيل معلمات جميع استدعاءات الطرق)، والإصلاح السريع (إصلاح خطأ في الإنتاج دون مراجعة App Store عبر مكتبات مثل JSPatch).

  • التحليلات التلقائية — swizzling UIViewController.viewDidAppear لإرسال أحداث عرض الشاشة بدون تكرار الكود في كل وحدة تحكم.
  • تسجيل طلبات الشبكة — swizzling NSURLSession.resume لتتبع جميع طلبات HTTP، بما في ذلك طلبات المكتبات الخارجية.
  • AOP (البرمجة الموجهة بالجوانب) — مكتبة Aspects تقوم بتبديل الطرق وتنفيذ كتلة كود قبل/بعد/بدلاً من الاستدعاء الأصلي.
  • الإصلاح السريع — استبدال تنفيذ طريقة معيبة بأخرى مصححة دون إعادة بناء التطبيق (محظور من App Review منذ 2020).
  • الاختبار والنماذج الوهمية — OCMock يستخدم swizzling لاستبدال الطرق بتنفيذات وهمية في اختبارات الوحدة.

كل من هذه السيناريوهات يعمل لأن swizzling يُطبق مركزياً. مكتبة تحليلات تقوم بـ swizzling مرة واحدة في +load، وتبدأ جميع مثيلات UIViewController في التطبيق بإرسال الأحداث. لا يحتاج المطور إلى إضافة كود في كل وحدة تحكم — هذا يقلل التكرار وخطر الأخطاء.

Method Swizzling على Android: الانعكاس ومعالجة البايت كود

على Android، swizzling الطرق بالمعنى الكلاسيكي لـ Objective-C مستحيل — Java/Kotlin تستخدم الإرسال الثابت عبر vtable. ومع ذلك، توجد آليات تحقق تأثيراً مشابهاً: Java Reflection لاستبدال التنفيذ في وقت التشغيل وGradle Transform API / ASM لتعديل البايت كود في وقت البناء.

kotlin
// Swizzling على Android عبر الانعكاس + companion object
class Logger {
    companion object {
        var originalImpl: (() -> Unit)? = null
    }

    fun log() {
        println("السجل الأصلي")
    }
}

// استبدال التنفيذ في وقت التشغيل عبر الانعكاس
fun swizzleLog() {
    val originalMethod = Logger::class.java
        .getDeclaredMethod("log")
    originalMethod.isAccessible = true

    Logger.originalImpl = {
        originalMethod.invoke(Logger())
    }

    // الاستبدال عبر دالة مضمنة
    println("swizzled: تم اعتراض السجل")
}

هذا الكود يستبدل سلوك طريقة log() عبر Java Reflection: getDeclaredMethod يصل إلى التنفيذ الخاص، isAccessible يعطل فحوصات الوصول. بدلاً من استدعاء log() مباشرة، يتم استدعاء غلاف ينفذ منطقاً إضافياً. ومع ذلك، يقوم Android بتحسين الطرق الساخنة عبر JIT — قد لا يعمل الانعكاس على أجزاء AOT المترجمة بالفعل.

نهج أكثر موثوقية هو معالجة البايت كود عبر Gradle Transform API أو AGP (Android Gradle Plugin) مع مكتبة ASM. يتم تعديل البايت كود في وقت الترجمة: ASM يضيف استدعاءات إلى كل طريقة في الفئة. هكذا تعمل أدوات تغطية الكود (JaCoCo) ومراقبة الأداء (Firebase Performance Monitoring).

مخاطر وأفضل ممارسات Method Swizzling

Method Swizzling هي تقنية عالية المخاطر. تعارض بين المكتبات: إذا قامت مكتبتان بتبديل نفس الطريقة، فإن ترتيب التنفيذ غير مضمون. عدم التوافق مع تحديثات iOS: إذا غيرت Apple التوقيع أو أزالت طريقة في إصدار iOS جديد، يؤدي swizzling إلى تعطل. عدم الرؤية في الكود: swizzling غير مرئي في تنفيذ الفئة، مما يعقد التصحيح.

الخطرالوصفالتخفيف
تعارض المكتباتمكتبتان تتبادلان viewDidAppear — إحداهما تعطل الأخرىالتحقق مما إذا كانت الطريقة قد تم تبديلها بالفعل عبر class_getInstanceMethod
التكرارإعادة swizzling لنفس الطريقة يسبب حلقة لا نهائيةاستخدام dispatch_once دائماً
تغيير التوقيعApple تغير توقيع الطريقة في إصدار iOS جديد — عدم تطابق IMPالاختبار على جميع إصدارات iOS المدعومة
عدم الرؤيةSwizzling لا يظهر في مكدس استدعاءات Xcodeتوثيق جميع عمليات swizzling في الكود
مراجعة App StoreApple ترفض التطبيقات ذات swizzling غير الموثقاستخدام APIs العامة فقط وتوثيق الغرض

أفضل الممارسات للswizzling الآمن تشمل: استدعاء التنفيذ الأصلي دائماً، إجراء swizzling بدقة في +load عبر dispatch_once، تسمية الطرق المستبدلة ببادئة (مثل s_originalMethodName)، توثيق كل عملية swizzling بهدفها. مكتبة Aspects تحل مشكلة التعارض من خلال التنفيذ المتسلسل للكتل قبل/بعد الطريقة الأصلية.

بدائل Method Swizzling في التطوير الحديث

بدائل method swizzling مفضلة لكود الإنتاج بسبب القدرة على التنبؤ والأمان. المفوضون والبروتوكولات (UIApplicationDelegate, UITableViewDelegate) يقدمون نقاط توسع صريحة دون تعديل وقت التشغيل. إنشاء الفئات الفرعية — إنشاء فئة فرعية من UIViewController مع تجاوز viewDidAppear — يعمل بشكل متوقع وليس له تعارضات.

SwiftUI وCombine يلغي الحاجة إلى swizzling: المعدلات (onAppear, onChange) تضيف السلوك بشكل تصريحي، دون تجاوز الطرق. في Android Jetpack Compose يحقق نفس الشيء من خلال التأثيرات (LaunchedEffect, SideEffect) والمعدلات. أطر AOP (AspectJ لنظام Android، InterposeKit لنظام iOS) توفر بديلاً آمناً مع weaving في وقت الترجمة.

وفقاً لبيانات Apple WWDC 2024، فإن وقت تشغيل Swift لا يدعم method swizzling على مستوى اللغة — طرق @objc dynamic يمكن تبديلها فقط من خلال Objective-C Runtime. تطبيقات Swift التي لا تستخدم @objc محمية تماماً من swizzling العرضي بواسطة مكتبات الطرف الثالث. هذا يجعل Swift أكثر أماناً ولكنه يحد من قدرات التجهيز في وقت التشغيل.

البدائل التصريحية في SwiftUI وCompose

معدلات SwiftUI (onAppear, onChange, onReceive) وتأثيرات Jetpack Compose (LaunchedEffect, SideEffect, DisposableEffect) تحل محل swizzling بالكامل لمهام واجهة المستخدم. توفر طريقة تصريحية ومتوقعة وقابلة للاختبار لإضافة سلوك شامل دون تعديل جدول الإرسال. في المشاريع الجديدة، توصي Apple وGoogle بهذا النهج بدلاً من الاعتراض في وقت التشغيل.

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

هل Method Swizzling آمن للإنتاج؟

Method Swizzling مقبول للإنتاج عند اتباع القواعد: dispatch_once للتنفيذ لمرة واحدة، استدعاء التنفيذ الأصلي، الاختبار على جميع إصدارات iOS والتوثيق. للمهام البسيطة، من الأفضل استخدام المفوضين أو إنشاء الفئات الفرعية. Swizzling في الإنتاج مبرر لمكتبات المراقبة والتحليلات.

كيف يختلف Swizzling عن AOP؟

Method Swizzling هي تقنية محددة لاستبدال IMP في جدول الإرسال. AOP (البرمجة الموجهة بالجوانب) هو نموذج يمكن أن يُستخدم فيه swizzling كإحدى الآليات. يشمل AOP أيضاً weaving وقت الترجمة (AspectJ)، الاعتراض القائم على الوكيل (Spring AOP) وتوليد الكود.

كيفية تصحيح المشاكل الناجمة عن Swizzling؟

استخدام نقطة توقف في objc_msgSend لتتبع جميع الرسائل. إضافة نقطة توقف رمزية على method_exchangeImplementations بشرط على اسم الفئة. أداة FLEX تظهر أي طرق الفئة تم تبديلها. للتحقق المنتظم، استخدام برنامج lldb النصي الذي يُخرج جدول إرسال الفئة.

هل يعمل Swizzling في Swift؟

Swift لا يدعم swizzling على مستوى اللغة. Method Swizzling يعمل فقط للطرق الموسومة بـ @objc dynamic، التي يتم ترجمتها عبر Objective-C Runtime. طرق Swift النقية (بدون @objc) تستخدم الإرسال الثابت ولا يمكن تبديلها — جدول الإرسال الخاص بها غير قابل للتعديل.

ما هي مكتبات iOS التي تستخدم Swizzling؟

Firebase Analytics (swizzling viewDidAppear للتتبع التلقائي للشاشات)، Amplitude، Mixpanel، FLEX (تفتيش واجهة المستخدم)، OHHTTPStubs (محاكاة طلبات الشبكة)، Aspects (إطار AOP). جميعها تقوم بـ swizzling في +load عبر dispatch_once مع استدعاء التنفيذ الأصلي.

الخلاصة

  • Method Swizzling — تبادل IMPs لطريقتين في جدول إرسال Objective-C Runtime عبر method_exchangeImplementations.
  • dispatch_once إلزامي لمنع إعادة swizzling والتكرار.
  • استدعاء التنفيذ الأصلي داخل الطريقة المستبدلة قاعدة أمان إلزامية.
  • على Android، يُستبدل swizzling بالانعكاس أو معالجة البايت كود عبر Gradle Transform / ASM.
  • المخاطر — تعارض المكتبات، عدم التوافق مع إصدارات iOS، عدم الرؤية في المصحح وحظر App Review للإصلاحات السريعة.
  • البدائل — المفوضون، الفئات الفرعية، معدلات SwiftUI، تأثيرات Jetpack Compose.
  • طرق Swift بدون @objc dynamic محمية من swizzling، مما يحسن الاستقرار ولكنه يحد من التجهيز في وقت التشغيل.

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

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

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

اقرأ أيضًا