我想强制用户使用我自己的初始化方法(例如
-(id)initWithString:(NSString*)foo;
)而不是基本的[[myObject alloc]init];
。
我该怎么做?
虽然当有人调用你的方法时很容易在运行时崩溃,但编译时检查会更好。
幸运的是,这在 Objective-C 中已经成为可能。
使用 LLVM,您可以将类中的任何方法声明为不可用,如下所示
- (void)aMethod __attribute__((unavailable("This method is not available")));
这会让编译器在尝试调用
aMethod
时抱怨。太棒了!
由于
- (id)init
只是一个普通方法,因此您可以通过这种方式禁止调用默认(或任何其他)初始化程序。
但请注意,这不能确保使用语言的动态方面调用方法,例如通过
[object performSelector:@selector(aMethod)]
等。在 init 的情况下,您甚至不会收到警告,因为init 方法是在其他类中定义的,编译器不够了解,无法向您发出未声明的选择器警告。
因此,为了防止这种情况发生,请确保 init 方法在被调用时崩溃(参见 Adam 的回答)。
如果您想在框架中禁止
- (id)init
,请确保也禁止 + (id)new
,因为这只会转发到 init。
Javi Soto 编写了一个小宏,以更快、更轻松地禁止使用指定的初始化程序,并提供更好的消息。您可以在这里找到它。
tl;博士
斯威夫特:
private init() {}
由于所有 Swift 类默认都包含内部 init,因此您可以将其更改为私有以防止其他类调用它。
目标C:
将其放入班级的 .h 文件中。
- (instancetype)init NS_UNAVAILABLE;
这依赖于操作系统定义,该定义阻止调用指定的方法。
接受的答案是不正确的 - 你可以做到这一点,而且非常简单,你只需要明确一点即可。这是一个例子:
您有一个名为“DontAllowInit”的类,您想阻止人们初始化它:
@implementation DontAllowInit
- (id)init
{
if( [self class] == [DontAllowInit class])
{
NSAssert(false, @"You cannot init this class directly. Instead, use a subclass e.g. AcceptableSubclass");
self = nil; // as per @uranusjr's answer, should assign to self before returning
}
else
self = [super init];
return nil;
}
说明:
工作完美。注意:我不建议将这种技术用于“普通应用程序”,因为通常您想使用协议。
然而......在编写库时......这种技术非常有价值:你经常想要“从他们自己手中拯救(其他开发人员)”,并且很容易 NSAssert 并告诉他们“哎呀!你试图分配/初始化错误类!尝试 X 类......”。
-(id) init
{
@throw [NSException exceptionWithName: @"MyExceptionName"
reason: @"-init is not allowed, use -initWithString: instead"
userInfo: nil];
}
-(id) initWithString: (NSString*) foo
{
self = [super init]; // OK because it calls NSObject's init, not yours
// etc
如果您记录了
-init
是不允许的,那么抛出异常是合理的,因此使用它是程序员错误。 然而,更好的答案是让 -init
调用 -initWtihString:
并使用一些合适的默认值,即
-(id) init
{
return [self initWithString: @""];
}
简短回答:你不能。
更长的答案:最佳实践是将最详细的初始化程序设置为指定的初始化程序,如here所述。然后“init”将使用正常的默认值调用该初始化程序。
另一种选择是“assert(0)”或在“init”内以另一种方式崩溃,但这不是一个好的解决方案。
我实际上投票赞成了 Adam 的答案,但想添加一些内容。
首先,强烈建议您在
init
中检查 NSObject
与 self
(如 nil
子类中自动生成的 init
方法所示)。另外,我不认为类对象保证像 ==
中那样“相等”。我这样做更像是
- (id)init
{
NSAssert(NO, @"You are doing it wrong.");
self = [super init];
if ([self isKindOfClass:[InitNotAllowedClass class]])
self = nil;
return self;
}
请注意,我使用
isKindOfClass:
来代替,因为恕我直言,如果此类不允许 init
,它也应该禁止其后代拥有它。如果它的子类之一想要它回来(这对我来说没有意义),它应该通过调用我指定的初始化程序来显式覆盖它。
但更重要的是,无论您是否采取上述方法,您都应该始终拥有适当的文档。您应该始终清楚地说明哪个方法是您指定的初始化程序,尽最大努力提醒其他人不要在文档中使用不适当的初始化程序,并信任其他用户/开发人员,而不是试图“拯救其他人的屁股”聪明的代码。
对于 Swift 5,使用
@available(*, unavailable, message: "This method is not available")
是一种方法,但在这种方法中你不能使用类内部的实例方法。我建议对无法访问的初始化程序使用 private 关键字。
对于继承自 NSObject 的类:
private override init() {
super.init()
}