سؤال

أنا في الأصل مبرمج Java وأعمل الآن مع Objective-C.أرغب في إنشاء فئة مجردة، ولكن لا يبدو أن هذا ممكن في Objective-C.هل هذا ممكن؟

إذا لم يكن الأمر كذلك، ما مدى الاقتراب من الفصل التجريدي الذي يمكنني الوصول إليه في Objective-C؟

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

المحلول

وعادة، والطبقة الهدف-C هي مجردة من الاتفاقية فقط، إذا كان المؤلف يوثق فئة كما مجردة، لا تستخدم فقط من دون شاء subclasses ترث منه. ليس هناك إنفاذ وقت الترجمة يمنع مثيل فئة مجردة، ولكن. في الواقع، ليس هناك ما يمنع المستخدم من توفير تطبيقات أساليب مجردة عن طريق فئة (أي في وقت التشغيل). يمكنك فرض المستخدم لتجاوز ما لا يقل عن أساليب معينة من خلال رفع استثناء في تلك تنفيذ الأساليب في فئة مجردة الخاص بك:

[NSException raise:NSInternalInconsistencyException 
            format:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)];

إذا بإرجاع طريقتك قيمة، انها أسهل قليلا لاستخدام

@throw [NSException exceptionWithName:NSInternalInconsistencyException
                               reason:[NSString stringWithFormat:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)]
                             userInfo:nil];

وكما فإنك لا تحتاج إلى إضافة عبارة إرجاع من أسلوب.

وإذا كانت فئة مجردة هو في حقيقة الأمر واجهة (أي لا يوجد لديه تطبيقات طريقة محددة)، وذلك باستخدام بروتوكول الهدف-C هو الخيار الأنسب.

نصائح أخرى

لا، لا توجد وسيلة لخلق فئة مجردة في الهدف-C.

ويمكنك يسخر فئة مجردة - بجعل الطرق / محددات الاتصال doesNotRecognizeSelector: وبالتالي رفع استثناء مما يجعل الطبقة غير صالحة للاستعمال

.

وعلى سبيل المثال:

- (id)someMethod:(SomeObject*)blah
{
     [self doesNotRecognizeSelector:_cmd];
     return nil;
}

ويمكنك أيضا القيام بذلك عن الحرف الأول.

ما عليك سوى الاطلاع على إجابة @Barry Wark أعلاه (والتحديث لنظام iOS 4.3) وترك هذا للرجوع إليه:

#define mustOverride() @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"%s must be overridden in a subclass/category", __PRETTY_FUNCTION__] userInfo:nil]
#define methodNotImplemented() mustOverride()

ثم في طرقك يمكنك استخدام هذا

- (void) someMethod {
     mustOverride(); // or methodNotImplemented(), same thing
}



ملحوظات: لست متأكدًا مما إذا كان جعل الماكرو يبدو كدالة C فكرة جيدة أم لا، لكنني سأحتفظ بها حتى أتعلم عكس ذلك.أعتقد أنه الأصح في الاستخدام NSInvalidArgumentException (بدلا من NSInternalInconsistencyException) لأن هذا هو ما يرميه نظام وقت التشغيل استجابةً له doesNotRecognizeSelector يتم استدعاؤه (انظر NSObject المستندات).

الحل الذي توصلت إليه هو:

  1. قم بإنشاء بروتوكول لكل ما تريده في صفك "المجرد".
  2. قم بإنشاء فئة أساسية (أو ربما نسميها مجردة) تنفذ البروتوكول.بالنسبة لجميع الطرق التي تريدها "مجردة"، قم بتنفيذها في ملف .m، ولكن ليس ملف .h.
  3. اجعل فصلك الفرعي يرث من الفصل الأساسي وينفذ البروتوكول.

بهذه الطريقة سيعطيك المترجم تحذيرًا بشأن أي طريقة في البروتوكول لم يتم تنفيذها بواسطة الفصل الفرعي الخاص بك.

إنها ليست موجزة كما هو الحال في Java، ولكنك تحصل على تحذير المترجم المطلوب.

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

- (void)performOperation:(id<Operation>)op
{
   // do something with operation
}

وأين المرجع يمكن أن يكون أي كائن تنفيذ بروتوكول العملية.

إذا كنت بحاجة إلى الفئة الأساسية مجردة بك أن تفعل أكثر من مجرد تحديد الأساليب، يمكنك إنشاء فئة العادية الهدف-C ومنعه من مثيل. مجرد تجاوز - (معرف) وظيفة الحرف الأول وجعله يعود صفر أو تأكيد (كاذبة). انها ليست الحل نظيفة جدا، ولكن منذ الهدف-C الحيوية بشكل كامل، وهناك حقا أي ما يعادل مباشرة إلى الفئة الأساسية مجردة.

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

ومع ذلك، لم يتم ذكر طريقتي المفضلة، ولا يوجد دعم أصلي لـ AFAIK في Clang الحالية، لذا ها أنا ذا...

أولًا وقبل كل شيء (كما أشار الآخرون سابقًا) تعد الفئات المجردة أمرًا غير شائع جدًا في Objective-C - نستخدم عادةً التركيب (أحيانًا من خلال التفويض) بدلاً من ذلك.ربما يكون هذا هو السبب وراء عدم وجود هذه الميزة بالفعل في اللغة/المترجم - باستثناء @dynamic الخصائص، التي تمت إضافتها إلى IIRC في ObjC 2.0 المصاحبة لمقدمة CoreData.

ولكن بالنظر إلى أنك (بعد التقييم الدقيق لموقفك!) توصلت إلى استنتاج مفاده أن التفويض (أو التكوين بشكل عام) ليس مناسبًا تمامًا لحل مشكلتك، فإليك الطريقة أنا افعلها:

  1. تنفيذ كل طريقة مجردة في الفئة الأساسية.
  2. جعل هذا التنفيذ [self doesNotRecognizeSelector:_cmd];
  3. …تليها __builtin_unreachable(); لإسكات التحذير الذي ستحصل عليه بالنسبة للطرق غير الفارغة، والذي يخبرك بأن "التحكم وصل إلى نهاية الدالة غير الفارغة دون عودة".
  4. إما الجمع بين الخطوات 2.و3.في ماكرو، أو التعليق التوضيحي -[NSObject doesNotRecognizeSelector:] استخدام __attribute__((__noreturn__)) في فئة دون تنفيذ حتى لا تحل محل التنفيذ الأصلي لتلك الطريقة، وقم بتضمين رأس تلك الفئة في PCH الخاص بمشروعك.

أنا شخصياً أفضّل إصدار الماكرو لأن ذلك يسمح لي بتقليل النموذج المعياري قدر الإمكان.

ها هو:

// Definition:
#define D12_ABSTRACT_METHOD {\
 [self doesNotRecognizeSelector:_cmd]; \
 __builtin_unreachable(); \
}

// Usage (assuming we were Apple, implementing the abstract base class NSString):
@implementation NSString

#pragma mark - Abstract Primitives
- (unichar)characterAtIndex:(NSUInteger)index D12_ABSTRACT_METHOD
- (NSUInteger)length D12_ABSTRACT_METHOD
- (void)getCharacters:(unichar *)buffer range:(NSRange)aRange D12_ABSTRACT_METHOD

#pragma mark - Concrete Methods
- (NSString *)substringWithRange:(NSRange)aRange
{
    if (aRange.location + aRange.length >= [self length])
        [NSException raise:NSInvalidArgumentException format:@"Range %@ exceeds the length of %@ (%lu)", NSStringFromRange(aRange), [super description], (unsigned long)[self length]];

    unichar *buffer = (unichar *)malloc(aRange.length * sizeof(unichar));
    [self getCharacters:buffer range:aRange];

    return [[[NSString alloc] initWithCharactersNoCopy:buffer length:aRange.length freeWhenDone:YES] autorelease];
}
// and so forth…

@end

كما ترون، يوفر الماكرو التنفيذ الكامل للطرق المجردة، مما يقلل الكمية الضرورية من النموذج المعياري إلى الحد الأدنى المطلق.

سيكون الخيار الأفضل هو اللوبي فريق كلانج لتوفير سمة مترجم لهذه الحالة، عبر طلبات الميزات.(الأفضل، لأن هذا من شأنه أيضًا تمكين تشخيصات وقت الترجمة لتلك السيناريوهات التي تستخدم فيها فئة فرعية، على سبيل المثال.NSIncrementalStore.)

لماذا اخترت هذه الطريقة

  1. إنها تنجز المهمة بكفاءة وبشكل مريح إلى حد ما.
  2. من السهل أن نفهم.(حسنا، ذلك __builtin_unreachable() قد يفاجئ الناس، ولكن من السهل فهمه أيضًا.)
  3. لا يمكن تجريدها في إصدارات الإصدار دون إنشاء تحذيرات أو أخطاء أخرى للمترجم - على عكس النهج الذي يعتمد على إحدى وحدات ماكرو التأكيد.

أعتقد أن النقطة الأخيرة تحتاج إلى بعض التوضيح:

يقوم بعض الأشخاص (معظمهم؟) بتجريد التأكيدات في إصدارات الإصدار.(أنا لا أتفق مع هذه العادة، ولكن هذه قصة أخرى…) ولكن الفشل في تنفيذ الطريقة المطلوبة هو أمر كذلك سيء, رهيب, خطأ, ، و في الأساس نهاية الكون لبرنامجك.لا يمكن لبرنامجك أن يعمل بشكل صحيح في هذا الصدد لأنه غير محدد، والسلوك غير المحدد هو أسوأ شيء على الإطلاق.وبالتالي، فإن القدرة على تجريد تلك التشخيصات دون إنشاء تشخيصات جديدة سيكون أمراً غير مقبول على الإطلاق.

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

وعن طريق @property و@dynamic يمكن أن تعمل أيضا. إذا قمت بتعريف خاصية ديناميكية ولا تعطي تطبيق الأسلوب مطابقة، كل شيء لا يزال تجميع دون التحذيرات، وستحصل على خطأ unrecognized selector في وقت التشغيل إذا حاولت الوصول إليه. هذا أساسا نفس الشيء كما يدعو [self doesNotRecognizeSelector:_cmd]، ولكن مع أقل بكثير الكتابة.

في كسكودي (باستخدام رنة الخ) أود أن استخدام __attribute__((unavailable(...))) لوضع علامة على فئات مجردة حتى تحصل على خطأ / تحذير إذا حاولت واستخدامها.

وويوفر بعض الحماية ضد بطريق الخطأ باستخدام هذه الطريقة.

مثال

في ل@interface الفئة الأساسية علامة الطرق "مجردة":

- (void)myAbstractMethod:(id)param1 __attribute__((unavailable("You should always override this")));

وأخذ هذا خطوة أخرى إلى الأمام، وأنا إنشاء ماكرو:

#define UnavailableMacro(msg) __attribute__((unavailable(msg)))

وهذا يتيح لك القيام بذلك:

- (void)myAbstractMethod:(id)param1 UnavailableMacro(@"You should always override this");

وكما قلت، هذا ليس حماية المترجم الحقيقي ولكن حان جيدة مثل ذهابك للحصول على اللغة التي لا تدعم أساليب مجردة.

ومبعثرة الجواب على السؤال حول في تعليق تحت الإجابات بالفعل. لذا، أنا مجرد تلخيص وتبسيط هنا.

OPTION1: بروتوكولات

إذا كنت ترغب في إنشاء فئة مجردة بدون استخدام تطبيق "البروتوكولات". الطبقات وراثة بروتوكول ملزمة لتنفيذ طرق في البروتوكول.

@protocol ProtocolName
// list of methods and properties
@end

OPTION2: قالب أسلوب نمط

إذا كنت ترغب في إنشاء فئة مجردة مع التنفيذ الجزئي مثل "نمط أسلوب قالب" ثم وهذا هو الحل. الهدف-C - طرق قالب نمط

وثمة بديل آخر

وانظروا فقط الفئة في خلاصة الدرجة وتأكيد أو استثناء، كل ما يتوهم.

@implementation Orange
- (instancetype)init
{
    self = [super init];
    NSAssert([self class] != [Orange class], @"This is an abstract class");
    if (self) {
    }
    return self;
}
@end

وهذا يزيل الحاجة إلى تجاوز init

و(أكثر من اقتراح ذات الصلة)

وأردت أن يكون وسيلة لترك مبرمج أعرف "لا ندعو من طفل" وتجاوز تماما (في حالتي لا تزال توفر بعض الوظائف الافتراضي نيابة عن الوالد عندما لا تكون ممتدة):

typedef void override_void;
typedef id override_id;

@implementation myBaseClass

// some limited default behavior (undesired by subclasses)
- (override_void) doSomething;
- (override_id) makeSomeObject;

// some internally required default behavior
- (void) doesSomethingImportant;

@end

وميزة هي أن مبرمج ستشهد "تجاوز" في الإعلان، وسوف يعرفون أنهم لا ينبغي أن تكون الدعوة [super ..].

ومنحت، فمن القبيح الحاجة إلى تحديد أنواع الإرجاع الفردية لهذا، ولكنه بمثابة إشارة بصرية جيدة بما فيه الكفاية، ويمكنك بسهولة عدم استخدام "override_" جزء في تعريف فئة فرعية.

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

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

إذا كنت تستخدم لاصطياد المترجم انتهاكات مثيل مجردة في لغات أخرى، ثم سلوك الهدف-C أمر مخيب للآمال.

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

وهنا هو النمط الذي تستخدمه للحصول على هذا النوع من التدقيق ثابت باستخدام اثنين من التقنيات لإخفاء المهيآت:

//
//  Base.h
#define UNAVAILABLE __attribute__((unavailable("Default initializer not available.")));

@protocol MyProtocol <NSObject>
-(void) dependentFunction;
@end

@interface Base : NSObject {
    @protected
    __weak id<MyProtocol> _protocolHelper; // Weak to prevent retain cycles!
}

- (instancetype) init UNAVAILABLE; // Prevent the user from calling this
- (void) doStuffUsingDependentFunction;
@end

//
//  Base.m
#import "Base.h"

// We know that Base has a hidden initializer method.
// Declare it here for readability.
@interface Base (Private)
- (instancetype)initFromDerived;
@end

@implementation Base
- (instancetype)initFromDerived {
    // It is unlikely that this becomes incorrect, but assert
    // just in case.
    NSAssert(![self isMemberOfClass:[Base class]],
             @"To be called only from derived classes!");
    self = [super init];
    return self;
}

- (void) doStuffUsingDependentFunction {
    [_protocolHelper dependentFunction]; // Use it
}
@end

//
//  Derived.h
#import "Base.h"

@interface Derived : Base
-(instancetype) initDerived; // We cannot use init here :(
@end

//
//  Derived.m
#import "Derived.h"

// We know that Base has a hidden initializer method.
// Declare it here.
@interface Base (Private)
- (instancetype) initFromDerived;
@end

// Privately inherit protocol
@interface Derived () <MyProtocol>
@end

@implementation Derived
-(instancetype) initDerived {
    self= [super initFromDerived];
    if (self) {
        self->_protocolHelper= self;
    }
    return self;
}

// Implement the missing function
-(void)dependentFunction {
}
@end

وربما هذا النوع من الحالات يجب أن يحدث فقط في وقت التطوير، ولذلك فإن هذا قد عمل:

- (id)myMethodWithVar:(id)var {
   NSAssert(NO, @"You most override myMethodWithVar:");
   return nil;
}

يمكنك استخدام الطريقة التي يقترحها @يار (مع بعض التعديل):

#define mustOverride() @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"%s must be overridden in a subclass/category", __PRETTY_FUNCTION__] userInfo:nil]
#define setMustOverride() NSLog(@"%@ - method not implemented", NSStringFromClass([self class])); mustOverride()

هنا سوف تحصل على رسالة مثل:

<Date> ProjectName[7921:1967092] <Class where method not implemented> - method not implemented
<Date> ProjectName[7921:1967092] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[<Base class (if inherited or same if not> <Method name>] must be overridden in a subclass/category'

أو التأكيد:

NSAssert(![self respondsToSelector:@selector(<MethodName>)], @"Not implemented");

في هذه الحالة سوف تحصل على:

<Date> ProjectName[7926:1967491] *** Assertion failure in -[<Class Name> <Method name>], /Users/kirill/Documents/Projects/root/<ProjectName> Services/Classes/ViewControllers/YourClass:53

يمكنك أيضًا استخدام البروتوكولات والحلول الأخرى - ولكن هذا أحد أبسط الحلول.

الكاكاو لا يقدم شيئا يسمى مجردة. يمكننا خلق مجردة من الدرجة التي يحصل فحص فقط في وقت التشغيل، وفي وقت الترجمة هذا ليس محددا.

وأنا عادة فقط تعطيل أسلوب الحرف الأول في فئة أريد أن الملخص:

- (instancetype)__unavailable init; // This is an abstract class.

وهذا سوف يولد خطأ في وقت الترجمة كلما استدعاء الحرف الأول في تلك الفئة. وبعد ذلك استخدام أساليب الفئة على كل شيء آخر.

الهدف-C لا يوجد لديه طريقة مضمنة لإعلان فئات مجردة.

من خلال تغيير ما اقترحه @redfood قليلاً من خلال تطبيق تعليق @dotToString، أصبح لديك بالفعل الحل الذي اعتمده Instagram IGListKit.

  1. قم بإنشاء بروتوكول لجميع الطرق التي ليس من المنطقي تعريفها في الفئة الأساسية (المجردة)، أي.يحتاجون إلى تطبيقات محددة في الأطفال.
  2. قم بإنشاء فئة أساسية (مجردة) ذلك لا تنفيذ هذا البروتوكول.يمكنك إضافة أي طرق أخرى إلى هذا الفصل يكون من المنطقي أن يكون لها تطبيق مشترك.
  3. في كل مكان في مشروعك، إذا كان الطفل من AbstractClass يجب أن يتم الإدخال أو الإخراج بطريقة ما، اكتبه كـ AbstractClass<Protocol> بدلاً من.

لأن AbstractClass لا ينفذ Protocol, ، الطريقة الوحيدة للحصول على AbstractClass<Protocol> المثال هو من خلال فئة فرعية.مثل AbstractClass وحدها لا يمكن استخدامها في أي مكان في المشروع، بل تصبح مجردة.

بالطبع، هذا لا يمنع المطورين غير الموصي بهم من إضافة طرق جديدة تشير ببساطة إلى AbstractClass, ، والذي سينتهي به الأمر بالسماح بمثيل للفئة المجردة (لم يعد بعد الآن).

مثال العالم الحقيقي: IGListKit لديه فئة أساسية IGListSectionController الذي لا ينفذ البروتوكول IGListSectionType, ومع ذلك، فإن كل طريقة تتطلب مثيلًا لتلك الفئة، تسأل فعليًا عن النوع IGListSectionController<IGListSectionType>.لذلك لا توجد طريقة لاستخدام كائن من النوع IGListSectionController عن أي شيء مفيد في إطارها.

في الواقع، الهدف-C لايوجد فئات مجردة، ولكن يمكنك استخدام على بروتوكولات لتحقيق التأثير نفسه. هنا هو عينة:

CustomProtocol.h

#import <Foundation/Foundation.h>

@protocol CustomProtocol <NSObject>
@required
- (void)methodA;
@optional
- (void)methodB;
@end

TestProtocol.h

#import <Foundation/Foundation.h>
#import "CustomProtocol.h"

@interface TestProtocol : NSObject <CustomProtocol>

@end

TestProtocol.m

#import "TestProtocol.h"

@implementation TestProtocol

- (void)methodA
{
  NSLog(@"methodA...");
}

- (void)methodB
{
  NSLog(@"methodB...");
}
@end

وهناك مثال بسيط لخلق فئة مجردة

// Declare a protocol
@protocol AbcProtocol <NSObject>

-(void)fnOne;
-(void)fnTwo;

@optional

-(void)fnThree;

@end

// Abstract class
@interface AbstractAbc : NSObject<AbcProtocol>

@end

@implementation AbstractAbc

-(id)init{
    self = [super init];
    if (self) {
    }
    return self;
}

-(void)fnOne{
// Code
}

-(void)fnTwo{
// Code
}

@end

// Implementation class
@interface ImpAbc : AbstractAbc

@end

@implementation ImpAbc

-(id)init{
    self = [super init];
    if (self) {
    }
    return self;
}

// You may override it    
-(void)fnOne{
// Code
}
// You may override it
-(void)fnTwo{
// Code
}

-(void)fnThree{
// Code
}

@end

ولا يمكنك فقط إنشاء مندوب؟

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

وبعد ذلك كلما تنفيذ مندوب الخاص بك (أي فئة مجردة) يتم تحذيرك من قبل المجمع ما اختياري وظائف إلزامية تحتاج إلى تعريف السلوك.

وهذا يبدو وكأنه فئة قاعدة مجردة بالنسبة لي.

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