题
我原本是一个Java程序员,他们现在的工作与目标。我想创建一个抽象的班级,但这似乎不可能在目标C这可能吗?
如果没有,怎么关闭一个抽象的类我可以获得在目标C?
解决方案
通常,Objective-C类只是按照惯例抽象<!>#8212;如果作者将一个类记录为抽象,那么不要在没有子类化的情况下使用它。但是,没有编译时强制实施可以防止抽象类的实例化。实际上,没有什么可以阻止用户通过类别(即在运行时)提供抽象方法的实现。您可以通过在抽象类中的那些方法实现中引发异常来强制用户至少覆盖某些方法:
[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中创建一个抽象类。
你可以通过使方法/选择器调用doNotRecognizeSelector来模拟一个抽象类:因此引发一个使该类无法使用的异常。
例如:
- (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
docs)。
我想出的解决方案是:
- 在<!> quot; abstract <!>中创建所需内容的协议。类
- 创建一个实现协议的基类(或者可能称之为抽象)。对于你想要的所有方法<!> quot; abstract <!> quot;在.m文件中实现它们,但不是.h文件。
- 让您的子类继承自基类并实现协议。 醇>
这样编译器会向您发出协议中任何未由您的子类实现的方法的警告。
它不像Java那样简洁,但你确实得到了所需的编译器警告。
来自 Omni Group邮件列表:
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那里<!>#8217;在当前的Clang中没有原生支持,所以我在这里<!>#8230;
首先,最重要的是(正如其他人已经指出的那样)抽象类在Objective-C中非常罕见<!>#8212;我们通常使用组合(有时通过委托)来代替。这可能就是为什么这样的特性在语言/编译器中已经存在<!>#8217; <!>#8212;除了@dynamic
属性之外,随着CoreData的引入,IIRC已添加到ObjC 2.0中。
但考虑到(在仔细评估了你的情况之后!)你得出的结论是,代表团(或一般的作文)不是<!>#8217;非常适合解决你的问题,这里<!>#8217 ; 我如何做到这一点:
- 在基类中实现每个抽象方法。
- 进行实施
[self doesNotRecognizeSelector:_cmd];
<!>#8230; - <!>#8230;然后
__builtin_unreachable();
默默警告你<!>#8217;我会得到非空方法,告诉你<!>#8220;控制到达无空函数的结束没有返回<!>#8221;。 - 在宏中组合步骤2.和3.或在类别中使用
-[NSObject doesNotRecognizeSelector:]
注释__attribute__((__noreturn__))
,无需实现,以便不替换该方法的原始实现,并包括项目中该类别的标题<!>#8217; s PCH。
醇>
- 有效地完成了工作,有点方便。
- <!>#8217;相当容易理解。 (好吧,
__builtin_unreachable()
可能会给人们带来惊喜,但<!>#8217也很容易理解。) - 如果没有生成其他编译器警告或错误,则无法在发布版本中剥离<!>#8212;不像<!>#8217; s基于其中一个断言宏的方法。 醇>
我个人更喜欢宏版本,因为它允许我尽可能地减少样板。
这是:
// 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。)
为什么选择此方法
最后一点需要一些解释,我想:
一些(大多数?)人在发布版本中删除断言。 (我不同意这种习惯,但是<!>#8217;另一个故事<!>#8230;)未能实施所需的方法<!>#8212;然而<!>#8212; 糟糕,可怕,错误,基本上是您的计划的 。你的程序在这方面无法正常工作,因为它是未定义的,未定义的行为是最糟糕的事情。因此,能够在不产生新诊断的情况下剥离这些诊断将是完全不可接受的。
<!>#8217;它已经够糟糕了,你无法为这样的程序员错误获得正确的编译时诊断,并且不得不求助于这些运行时发现,但是如果你可以在发布时使用它构建,为什么首先尝试使用抽象类?
使用@property
和@dynamic
也可以。如果声明一个动态属性并且没有给出匹配的方法实现,那么所有内容仍然会在没有警告的情况下进行编译,如果您尝试访问它,则会在运行时遇到unrecognized selector
错误。这与调用[self doesNotRecognizeSelector:_cmd]
基本相同,但键入的次数要少得多。
在Xcode中(使用clang等)我喜欢使用__attribute__((unavailable(...)))
标记抽象类,以便在尝试使用它时收到错误/警告。
它可以防止意外使用该方法。
实施例
在基类@interface
中标记<!> quot; abstract <!>;方法:
- (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");
就像我说的,这不是真正的编译器保护,但它与你不会使用不支持抽象方法的语言一样好。
问题的答案分散在已经给出的答案的评论中。所以,我只是在这里进行总结和简化。
选项1:协议
如果要创建一个没有实现的抽象类,请使用“Protocols”。继承协议的类必须实现协议中的方法。
@protocol ProtocolName
// list of methods and properties
@end
选项2:模板方法模式
如果要创建一个具有部分实现的抽象类,如<!>“;模板方法模式<!>”;那么这就是解决方案。 Objective-C - 模板方法模式?
另一种选择
只需检查Abstract类中的类以及Assert或Exception,无论您喜欢什么。
@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 ..]
。
当然,为此定义单独的返回类型是很难看的,但它可以作为一个足够好的视觉提示,你很容易就不能使用<!> quot; override _ <!> quot;部分在子类定义中。
当扩展是可选的时,类当然仍然可以有默认实现。但是,与其他答案一样,在适当时实现运行时异常,例如抽象(虚拟)类。
建立像这样的编译器提示会很好,甚至提示何时最好预先/后调用超级工具,而不是必须挖掘注释/文档或者......假设。
如果您习惯于在其他语言中捕获抽象实例化违规的编译器,那么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;
}
你可以使用一种方法拟议通过 @亚尔 (与一些修改):
#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没有用于声明抽象类的内置方法。
通过应用@ dotToString的评论来改变@redfood的建议,你实际上已经采用了Instagram的 IGListKit 一>
- 为所有在基础(抽象)类中定义无意义的方法创建协议,即它们需要在子代中实现特定的实现。
- 创建一个不实现此协议的基类(抽象)类。您可以将任何其他有意义的方法添加到此类中以实现通用实现。
- 项目中的任何地方,如果必须通过某种方法输入或输出来自
AbstractClass
的孩子,请将其键入AbstractClass<Protocol>
。
醇>
因为Protocol
没有实现IGListSectionController
,所以拥有IGListSectionType
实例的唯一方法是通过子类化。由于IGListSectionController<IGListSectionType>
单独不能在项目的任何地方使用,它变得抽象。
当然,这并不妨碍未经修改的开发人员添加仅仅引用<=>的新方法,这最终会允许(不再是)抽象类的实例。
真实世界的例子: IGListKit 有一个基类<=>,它没有实现协议< =>,但是每个需要该类实例的方法实际上都要求输入类型<=>。因此,没有办法将类型<=>的对象用于其框架中有用的任何东西。
实际上,Objective-C没有抽象类,但您可以使用 Protocols 来实现相同的效果。以下是样本:
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
你不能只创建一个委托吗?
委托就像一个抽象基类,在某种意义上说你需要定义哪些函数,但你实际上并没有定义它们。
然后,无论何时实现委托(即抽象类),编译器都会警告您需要为可定义行为的可选函数和必需函数。
对我来说这听起来像是一个抽象的基类。