我最初是一名 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];
这样您就不需要从该方法中添加 return 语句。
如果抽象类真的是一个接口(即没有具体的方法实现),那么使用Objective-C 协议是更合适的选择。
不,没有办法在 Objective-C 中创建抽象类。
您可以模拟一个抽象类 - 通过使方法/选择器调用 doesNotRecognizeSelector: 并因此引发异常使该类不可用。
例如:
- (id)someMethod:(SomeObject*)blah
{
[self doesNotRecognizeSelector:_cmd];
return nil;
}
您也可以对 init 执行此操作。
只是重复上面@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
文档)。
我想出的解决方案是:
这样,编译器将为您的子类未实现的协议中的任何方法发出警告。
它不像 Java 那样简洁,但您确实会收到所需的编译器警告。
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 函数并使其返回 nil 或 assert(false)。这不是一个非常干净的解决方案,但由于 Objective-C 是完全动态的,因此实际上没有直接等效于抽象基类的方法。
这个帖子有点老了,我想分享的大部分内容已经在这里了。
但是,没有提到我最喜欢的方法,而且 AFAIK 在当前的 Clang 中没有原生支持,所以我开始......</p>
首先,最重要的是(正如其他人已经指出的那样)抽象类在 Objective-C 中非常少见——我们通常使用组合(有时通过委托)来代替。这可能是语言/编译器中尚不存在此类功能的原因——除了@dynamic
属性,IIRC 已随着 CoreData 的引入而添加到 ObjC 2.0 中。
但是鉴于(在仔细评估了您的情况之后!)您得出的结论是委派(或一般的组合)不太适合解决您的问题,这就是我的做法:
[self doesNotRecognizeSelector:_cmd];
……</li>
__builtin_unreachable();
将非 void 方法的警告静音,告诉您“控制已到达非 void 函数的结尾而没有返回”。-[NSObject doesNotRecognizeSelector:]
using ,以免替换该方法的原始实现,并将该类别的标头包含在项目的 PCH 中。__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
如您所见,宏提供了抽象方法的完整实现,将所需的样板数量减少到绝对最小值。
更好的选择是游说 Clang 团队通过功能请求为这种情况提供编译器属性。(更好,因为这也将为您子类化的那些场景启用编译时诊断,例如 NSIncrementalStore。)
__builtin_unreachable()
可能会让人们感到惊讶,但这也很容易理解。)最后一点需要一些解释,我猜:
一些(大多数?)人在发布版本中剥离断言。(我不同意这种习惯,但那是另一回事了……) 未能实现所需的方法——然而——是不好的、可怕的、错误的,并且基本上是你的程序的世界末日。你的程序在这方面不能正常工作,因为它是未定义的,未定义的行为是最糟糕的事情。因此,能够在不生成新诊断的情况下剥离这些诊断将是完全不可接受的。
您无法为此类程序员错误获得适当的编译时诊断,这已经够糟糕了,并且必须在运行时发现这些错误,但是如果您可以在发布版本中对其进行修复,为什么要尝试在第一名?
使用@property
并且@dynamic
也可以工作。如果你声明了一个动态属性并且没有给出一个匹配的方法实现,那么一切仍然会在没有警告的情况下编译,unrecognized selector
如果你尝试访问它,你会在运行时得到一个错误。这与 call 本质上是一样的[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");
就像我说的,这不是真正的编译器保护,但它与您使用不支持抽象方法的语言一样好。
问题的答案分散在已经给出的答案下的评论中。所以,我在这里只是总结和简化。
如果您想创建一个没有实现的抽象类,请使用“协议”。继承协议的类必须实现协议中的方法。
@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_”部分。
当然,当扩展是可选的时,类仍然可以具有默认实现。但就像其他答案所说,在适当的时候实现一个运行时异常,比如抽象(虚拟)类。
最好内置像这样的编译器提示,甚至提示何时最好预先/后调用 super 的实现,而不必挖掘评论/文档或......假设。
如果您习惯于编译器在其他语言中捕获抽象实例化违规,那么 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
可能这种情况应该只发生在开发时,所以这可能有效:
- (id)myMethodWithVar:(id)var {
NSAssert(NO, @"You most override myMethodWithVar:");
return nil;
}
您可以使用@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
您也可以使用协议和其他解决方案 - 但这是最简单的解决方案之一。
Cocoa不提供任何称为抽象的东西。我们可以创建一个仅在运行时检查的类摘要,而在编译时不检查。
我通常只是禁用我想要抽象的类中的 init 方法:
- (instancetype)__unavailable init; // This is an abstract class.
每当您在该类上调用 init 时,这将在编译时生成错误。然后我将类方法用于其他所有内容。
Objective-C 没有内置的方法来声明抽象类。
事实上,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
通过应用@dotToString 的评论稍微改变@redfood 的建议,您实际上拥有Instagram 的IGListKit采用的解决方案。
AbstractClass
在您的项目中的任何地方,如果必须通过某种方法输入或输出子 from ,请键入它AbstractClass<Protocol>
。因为AbstractClass
没有实现Protocol
,所以拥有AbstractClass<Protocol>
实例的唯一方法是子类化。由于AbstractClass
单独不能在项目的任何地方使用,它变得抽象。
当然,这并不能阻止不明智的开发人员添加简单地引用的新方法AbstractClass
,这最终会允许(不再是)抽象类的实例。
现实世界的例子:IGListKit有一个IGListSectionController
没有实现协议的基类IGListSectionType
,但是每个需要该类实例的方法实际上都需要 type IGListSectionController<IGListSectionType>
。因此,无法将类型对象IGListSectionController
用于其框架中有用的任何东西。
创建抽象类的简单示例
// 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
你不能只创建一个代表吗?
委托就像一个抽象基类,你说需要定义哪些函数,但实际上并没有定义它们。
然后,每当您实现委托(即抽象类)时,编译器都会警告您需要为其定义行为的可选函数和强制函数。
对我来说,这听起来像是一个抽象的基类。