Я спочатку Java-програміст, який зараз працює з Objective-C. Я хотів би створити абстрактний клас, але це видається неможливим в Objective-C. Чи можливо це?
Якщо ні, то наскільки близько до абстрактного класу я можу потрапити в Objective-C?
Я спочатку Java-програміст, який зараз працює з Objective-C. Я хотів би створити абстрактний клас, але це видається неможливим в Objective-C. Чи можливо це?
Якщо ні, то наскільки близько до абстрактного класу я можу потрапити в Objective-C?
Відповіді:
Зазвичай клас Objective-C є абстрактним лише за умовами - якщо автор документує клас як абстрактний, просто не використовуйте його без підкласифікації. Однак не існує примусового виконання часу, що заважає створювати абстрактний клас. Насправді, ніщо не заважає користувачеві забезпечувати реалізацію абстрактних методів через категорію (тобто під час виконання). Ви можете змусити користувача принаймні перевизначити певні методи, створивши виняток із реалізації цих методів у вашому абстрактному класі:
[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];
тому що вам не потрібно додавати операцію повернення з методу.
Якщо абстрактний клас справді є інтерфейсом (тобто не має конкретних реалізацій методу), більш правильним варіантом є використання протоколу Objective-C.
@protocol
визначенні, але ви не можете їх визначити.
+ (instancetype)exceptionForCallingAbstractMethod:(SEL)selector
це працює дуже добре.
doesNotRecognizeSelector
.
Ні, немає ніякого способу створити абстрактний клас в Objective-C.
Ви можете знущатися над абстрактним класом - зробивши виклики методів / селекторів doesNotRecognizeSelector: і тому підняти виняток, що робить клас непридатним.
Наприклад:
- (id)someMethod:(SomeObject*)blah
{
[self doesNotRecognizeSelector:_cmd];
return nil;
}
Ви також можете зробити це для init.
NSObject
посилання пропонує використовувати це там, де ви НЕ хочете успадковувати метод, а не примушувати переосмислення методу. Хоча це може бути те саме, можливо :)
Тільки сфальсифікуйте на відповідь @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
Документи).
universe.thing
але він розширюється на [Universe universe].thing
. Велике задоволення, заощадження тисяч букв коду ...
#define mustOverride() @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"%s must be overridden in a subclass/category", __PRETTY_FUNCTION__] userInfo:nil]
.
__PRETTY_FUNCTION__
всюди через макрос DLog (...), як тут пропонується: stackoverflow.com/a/969291/38557 .
#define setMustOverride() NSLog(@"%@ - method not implemented", NSStringFromClass([self class])); mustOverride()
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[BaseDynamicUIViewController localizeUI] must be overridden in a subclass/category'
у випадку, якщо я запропонував вам також отримати, HomeViewController - method not implemented
де HomeViewController успадкував від Base - це дамо більше інформації
Я придумав таке рішення:
Таким чином компілятор подасть вам попередження щодо будь-якого методу в протоколі, який не реалізований вашим дочірнім класом.
Це не так просто, як у Java, але ви отримуєте потрібне попередження компілятора.
NSObject
). Якщо потім помістити протокол і декларацію базового класу в один і той же файл заголовка, він майже не відрізняється від абстрактного класу.
Із списку розсилки групи Omni :
На даний момент у Objective-C немає абстрактної конструкції компілятора, як Java.
Отже, все, що вам потрібно зробити - це визначити абстрактний клас як будь-який інший звичайний клас та реалізувати заглушки методів для абстрактних методів, які або порожні, або повідомляють про непідтримку для селектора. Наприклад...
- (id)someMethod:(SomeObject*)blah
{
[self doesNotRecognizeSelector:_cmd];
return nil;
}
Я також роблю наступне, щоб запобігти ініціалізації абстрактного класу через ініціалізатор за замовчуванням.
- (id)init
{
[self doesNotRecognizeSelector:_cmd];
[self release];
return nil;
}
Замість того, щоб намагатися створити абстрактний базовий клас, подумайте про використання протоколу (подібного до інтерфейсу Java). Це дозволяє визначити набір методів, а потім прийняти всі об'єкти, які відповідають протоколу, та реалізувати методи. Наприклад, я можу визначити протокол Operation, а потім мати таку функцію:
- (void)performOperation:(id<Operation>)op
{
// do something with operation
}
Де op може бути будь-який об'єкт, що реалізує протокол Operation.
Якщо вам потрібен ваш абстрактний базовий клас, щоб не просто визначити методи, ви можете створити звичайний клас Objective-C і запобігти його інстанцізації. Просто замініть функцію - (id) init і змусьте її повернути нуль або затвердити (помилково). Це не дуже чисте рішення, але оскільки Objective-C є повністю динамічним, насправді немає прямого еквівалента абстрактному базовому класу.
Ця тема нині стара, і більшість того, що я хочу поділитися, вже є тут.
Однак, мій улюблений метод не згадується, і AFAIK не має вбудованої підтримки в поточному Clang, тож ось я іду…
По-перше, і головне (як уже вказували інші) абстрактні класи - це щось дуже незвичне в Objective-C - ми зазвичай використовуємо композицію (іноді через делегування). Це, мабуть, причина, чому така функція вже не існує в мові / компіляторі - крім@dynamic
властивостей, які IIRC були додані в ObjC 2.0, що супроводжує введення CoreData.
Але враховуючи, що (після ретельної оцінки вашої ситуації!) Ви дійшли висновку, що делегація (або склад взагалі) не дуже підходить для вирішення вашої проблеми, ось як я це роблю:
[self doesNotRecognizeSelector:_cmd];
...__builtin_unreachable();
замовчується попередження, яке ви отримаєте щодо недійсних методів, говорячи про те, що "контроль досягнуто кінця недійсної функції без повернення".-[NSObject doesNotRecognizeSelector:]
використовуючи __attribute__((__noreturn__))
категорію без реалізації, щоб не замінити оригінальну реалізацію цього методу, і включіть заголовок цієї категорії до ПКН вашого проекту.Я особисто віддаю перевагу макро-версії, оскільки це дозволяє мені максимально зменшити котельну плиту.
Ось:
// 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.)
__builtin_unreachable()
може здивувати людей, але це теж досить легко зрозуміти.)Останній пункт потребує певного пояснення:
Деякі (більшість?) Людей знімають твердження у версії версій. (Я не погоджуюся з цією звичкою, але це вже інша історія…) Якщо не реалізувати потрібний метод - однак - це погано , страшно , неправильно і, в основному, кінець Всесвіту для вашої програми. Ваша програма не може правильно працювати в цьому плані, оскільки вона не визначена, а невизначена поведінка - це найгірше, що коли-небудь було. Отже, можливість зняти діагностику без створення нової діагностики було б абсолютно неприйнятним.
Це досить погано, що ви не можете отримати належну діагностику часу компіляції для таких помилок програміста, і вам доведеться вдатися до виявлення під час запуску для них, але якщо ви можете зафіксувати це у версії версій, чому спробуйте мати абстрактний клас у першість?
__builtin_unreachable();
дорогоцінний камінь робить цю роботу ідеальною. Макрос робить його самодокументованим і поведінка відповідає тому, що станеться, якщо ви викликаєте відсутній метод на об'єкті.
Використання @property
і @dynamic
може також працювати. Якщо ви оголосите динамічне властивість і не надаєте реалізацію відповідного методу, все одно складеться без попереджень, і ви отримаєте unrecognized selector
помилку під час виконання, якщо спробуєте отримати доступ до нього. Це по суті те саме, що і дзвінки [self doesNotRecognizeSelector:_cmd]
, але із значно меншим набором тексту.
У Xcode (за допомогою clang тощо) я люблю використовувати __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");
Як я вже говорив, це не справжній захист компілятора, але він настільки ж хороший, як і ваш спосіб перейти на мову, яка не підтримує абстрактних методів.
-init
у вашому підкласі є метод.
Відповідь на запитання розкидається навколо у коментарях під уже даними відповідями. Отже, я тут просто підсумовую і спрощую.
Якщо ви хочете створити абстрактний клас без реалізації, використовуйте 'Protocols'. Класи, що успадковують протокол, зобов'язані реалізувати методи в протоколі.
@protocol ProtocolName
// list of methods and properties
@end
Якщо ви хочете створити абстрактний клас з частковою реалізацією на зразок "Шаблон методу шаблону", то це рішення. Objective-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_" у визначенні підкласу.
Звичайно, клас може все ще мати реалізацію за замовчуванням, коли розширення не є обов'язковим. Але як говорять інші відповіді, застосовуйте виняток під час виконання, коли це доречно, як для абстрактних (віртуальних) класів.
Було б непогано мати такі підказки для компілятора, як цей, навіть натяки на те, коли найкраще заздалегідь / після виклику здійснити реалізатор супер, замість того, щоб копати коментарі / документацію чи ... припускати.
Якщо ви звикли до того, що компілятор вловлює абстрактні порушення інстанцій іншими мовами, то поведінка Objective-C невтішна.
Як пізню мову зв’язування, зрозуміло, що Objective-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
Ви можете використовувати метод, запропонований @Yar (з деякою модифікацією):
#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
Також ви можете використовувати протоколи та інші рішення - але це одне з найпростіших.
Какао не дає нічого, що називається абстрактним. Ми можемо створити конспект класу, який перевіряється лише під час виконання, а під час компіляції це не перевіряється.
Зазвичай я просто відключаю метод init у класі, який я хочу абстрагувати:
- (instancetype)__unavailable init; // This is an abstract class.
Це призведе до помилки під час компіляції кожного разу, коли ви викликаєте init у цьому класі. Тоді я використовую методи класу для всього іншого.
У Objective-C немає вбудованого способу декларування абстрактних класів.
Змінивши трохи те, що запропонував @redfood, застосувавши коментар @ dotToString, ви насправді маєте рішення, прийняте IGListKit Instagram .
AbstractClass
має бути введена або виведена яким-небудь методом, введіть її як AbstractClass<Protocol>
замість неї.Оскільки AbstractClass
не реалізується Protocol
, єдиний спосіб мати AbstractClass<Protocol>
екземпляр - це підкласифікація. Оскільки AbstractClass
поодинці не можна використовувати ніде в проекті, він стає абстрактним.
Звичайно, це не заважає розробникам без AbstractClass
дозволу додавати нові методи, що посилаються просто на , що в результаті дозволить отримати екземпляр (вже не) абстрактного класу.
Приклад реального світу: IGListKit має базовий клас, IGListSectionController
який не реалізує протокол IGListSectionType
, проте кожен метод, який вимагає екземпляр цього класу, насправді запитує тип IGListSectionController<IGListSectionType>
. Тому немає можливості використовувати об’єкт типу IGListSectionController
для нічого корисного в їх рамках.
Насправді в Objective-C немає абстрактних класів, але ви можете використовувати протоколи для досягнення того ж ефекту. Ось зразок:
#import <Foundation/Foundation.h>
@protocol CustomProtocol <NSObject>
@required
- (void)methodA;
@optional
- (void)methodB;
@end
#import <Foundation/Foundation.h>
#import "CustomProtocol.h"
@interface TestProtocol : NSObject <CustomProtocol>
@end
#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
Ви не можете просто створити делегата?
Делегат - це як абстрактний базовий клас у тому сенсі, що ви говорите, які функції потрібно визначити, але ви насправді не визначаєте їх.
Тоді, коли ви реалізуєте свого делегата (тобто абстрактного класу), компілятор попереджає про те, які необов’язкові та обов'язкові функції вам потрібно визначити для поведінки.
Це для мене звучить як абстрактний базовий клас.