سؤال

أرغب في تحديد بروتوكول Objective-C مع روتين اختياري. عندما لا يتم تنفيذ الروتين بواسطة فصل يتوافق مع البروتوكول ، أود استخدام تطبيق افتراضي في مكانه. هل هناك مكان في البروتوكول نفسه حيث يمكنني تحديد هذا التنفيذ الافتراضي؟ إذا لم يكن الأمر كذلك ، فما هي أفضل ممارسة لتقليل نسخ ولصق هذا التنفيذ الافتراضي في كل مكان؟

هل كانت مفيدة؟

المحلول

بروتوكولات الهدف-C ليس لها أي تكاليف للتطبيقات الافتراضية. إنها مجموعات بحتة من إعلانات الطريقة التي يمكن تنفيذها بواسطة فئات أخرى. تتمثل الممارسة القياسية في Objective -C في اختبار كائن في وقت التشغيل لمعرفة ما إذا كان يستجيب للمحدد المعطى قبل استدعاء هذه الطريقة عليها ، باستخدام -[nsobject revieDstoselector:]. إذا لم يستجب كائن E للمحدد المحدد ، فلن يتم استدعاء الطريقة.

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

هناك طريقة أخرى تتمثل في جعل هذه الطريقة مطلوبة في البروتوكول ، وتوفير التطبيقات الافتراضية في الفطريات الفائقة لأي فئات قد لا ترغب في توفيرها لتنفيذ محدد.

من المحتمل أيضًا أن يكون هناك خيارات أخرى أيضًا ، ولكن بشكل عام لا توجد ممارسة قياسية معينة في Objective-C ، باستثناء ربما لا تتصل بالطريقة المعطاة إذا لم يتم تنفيذها بواسطة الكائن ، وفقًا للفقرة الأولى ، أعلاه .

نصائح أخرى

لا توجد طريقة قياسية للقيام بذلك لأن البروتوكولات يجب ألا تحدد أي تطبيقات.

نظرًا لأن Objective-C يأتي مع وقت تشغيل أنيق ، يمكنك بالطبع إضافة مثل هذا السلوك إذا كنت تعتقد حقًا أنك بحاجة إلى القيام بذلك بهذه الطريقة (وليس هناك احتمال من خلال تحقيق ذلك بالميراث).

لنفترض أنك أعلنت myprotocol ، ثم فقط أضف واجهة تحمل نفس الاسم في ملف .h بموجب إعلان البروتوكول:

@interface MyProtocol : NSObject <MyProtocol>

+ (void)addDefaultImplementationForClass:(Class)conformingClass;

@end

وإنشاء ملف تنفيذ مقابل (باستخدام Maobjcruntime لقدرة على القراءة هنا ، لكن وظائف وقت التشغيل القياسية لن تكون رمزًا أكثر بكثير):

@implementation MyProtocol

+ (void)addDefaultImplementationForClass:(Class)conformingClass {
  RTProtocol *protocol = [RTProtocol protocolWithName:@"MyProtocol"];
  // get all optional instance methods
  NSArray *optionalMethods = [protocol methodsRequired:NO instance:YES];
  for (RTMethod *method in optionalMethods) {
    if (![conformingClass rt_methodForSelector:[method selector]]) {
      RTMethod *myMethod = [self rt_methodForSelector:[method selector]];
      // add the default implementation from this class
      [conformingClass rt_addMethod:myMethod];
    }
  }
}

- (void)someOptionalProtocolMethod {
  // default implementation
  // will be added to any class that calls addDefault...: on itself
}

ثم عليك فقط الاتصال

[MyProtocol addDefaultImplementationForClass:[self class]];

في التهيئة لفصلك المطابق للبروتوكول وسيتم إضافة جميع الطرق الافتراضية.

طريقة رائعة حقًا هي استخدام وقت التشغيل. عند بدء التشغيل ، في وقت مبكر جدًا من تنفيذ البرنامج ، قم بما يلي:

  1. تعداد جميع الفصول ، ابحث عن الفصول التي تنفذ البروتوكول
  2. تحقق مما إذا كان الفصل ينفذ طريقة
  3. إذا لم يكن كذلك ، أضف إلى الفئة التنفيذ الافتراضي

يمكن تحقيق ذلك دون الكثير من المتاعب.

كما ذكر ريان ، لا توجد تطبيقات افتراضية للبروتوكولات ، فإن خيار آخر للتنفيذ في الطبقة الفائقة هو تنفيذ نوع من "معالج" فئة يمكن احتواءها في أي فئة ترغب تنفيذ المعالجات الافتراضية.

انتهى بي الأمر بإنشاء ماكرو له تنفيذ افتراضي للطريقة.

لقد حددته في ملف رأس البروتوكول ، ثم إنه مجرد خط واحد في كل تطبيق.

وبهذه الطريقة ، لا يتعين علي تغيير التطبيق عدة أماكن ، ويتم ذلك في وقت الترجمة ، لذلك لا يوجد أي وقت في وقت التشغيل ضروريًا.

وأنا أتفق مع حل "WM" لطيف للغاية هو وضع جميع التطبيقات الافتراضية في واجهة (بنفس اسم البروتوكول). في طريقة "+تهيئة" أي فئة فرعية ، يمكنه ببساطة نسخ أي طرق غير مستغلة من الواجهة الافتراضية إلى نفسها.

عملت وظائف المساعد التالية بالنسبة لي

#import <objc/runtime.h>

// Get the type string of a method, such as "v@:".
// Caller must allocate sufficent space. Result is null terminated.
void getMethodTypes(Method method, char*result, int maxResultLen)
{
    method_getReturnType(method, result, maxResultLen - 1);
    int na = method_getNumberOfArguments(method);
    for (int i = 0; i < na; ++i)
    {
        unsigned long x = strlen(result);
        method_getArgumentType(method, i, result + x, maxResultLen - 1 - x);
    }
}

// This copies all the instance methods from one class to another
// that are not already defined in the destination class.
void copyMissingMethods(Class fromClass, Class toClass)
{
    // This gets the INSTANCE methods only
    unsigned int numMethods;
    Method* methodList = class_copyMethodList(fromClass, &numMethods);
    for (int i = 0; i < numMethods; ++i)
    {
        Method method = methodList[i];
        SEL selector = method_getName(method);
        char methodTypes[50];
        getMethodTypes(method, methodTypes, sizeof methodTypes);

        if (![toClass respondsToSelector:selector])
        {
            IMP methodImplementation = class_getMethodImplementation(fromClass, selector);
            class_addMethod(toClass, selector, methodImplementation, methodTypes);
        }
    }
    free(methodList);
}

ثم تسميها في مُعمى الفصل مثل ...

@interface Foobar : NSObject<MyProtocol>  
@end

@implementation Foobar
+(void)initialize
{
    // Copy methods from the default
    copyMissingMethods([MyProtocol class], self);
}
@end

سوف يمنحك Xcode تحذيرات حول طرق Foobar المفقودة ، ولكن يمكنك تجاهلها.

هذه التقنية تنسخ الأساليب فقط ، وليس Ivars. إذا كانت الأساليب تصل إلى أعضاء البيانات غير الموجودين ، فيمكنك الحصول على أخطاء غريبة. يجب عليك التأكد من أن البيانات متوافقة مع الكود. يبدو الأمر كما لو كنت قد قمت بـ REINTERPRET_CAST من FOOBAR إلى MyProtocol.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top