如何禁止 NSObject 中的基本 init 方法

问题描述 投票:0回答:7

我想强制用户使用我自己的初始化方法(例如

-(id)initWithString:(NSString*)foo;
)而不是基本的
[[myObject alloc]init];

我该怎么做?

ios objective-c nsobject
7个回答
20
投票

这里的所有其他答案都已过时。现在有一种方法可以正确执行此操作!

虽然当有人调用你的方法时很容易在运行时崩溃,但编译时检查会更好。

幸运的是,这在 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 编写了一个小宏,以更快、更轻松地禁止使用指定的初始化程序,并提供更好的消息。您可以在这里找到它。


18
投票

tl;博士

斯威夫特

private init() {}

由于所有 Swift 类默认都包含内部 init,因此您可以将其更改为私有以防止其他类调用它。

目标C:

将其放入班级的 .h 文件中。

- (instancetype)init NS_UNAVAILABLE;

这依赖于操作系统定义,该定义阻止调用指定的方法。


9
投票

接受的答案是不正确的 - 你可以做到这一点,而且非常简单,你只需要明确一点即可。这是一个例子:

您有一个名为“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;
}

说明:

  1. 当你调用[super init]时,被分配的类是SUBCLASS。
  2. “self”是实例 - 即被初始化的东西
  3. “[self class]”是被实例化的类 - 当子类调用 [super init] 时它将成为子类,或者当使用普通 [[SuperClass alloc] init] 调用超级类时它将成为超级类
  4. 所以,当超类收到“init”调用时,它只需要检查 alloc'd 类是否与它自己的类相同

工作完美。注意:我不建议将这种技术用于“普通应用程序”,因为通常您想使用协议。

然而......在编写库时......这种技术非常有价值:你经常想要“从他们自己手中拯救(其他开发人员)”,并且很容易 NSAssert 并告诉他们“哎呀!你试图分配/初始化错误类!尝试 X 类......”。


3
投票
-(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: @""];
}

0
投票

简短回答:你不能。

更长的答案:最佳实践是将最详细的初始化程序设置为指定的初始化程序,如here所述。然后“init”将使用正常的默认值调用该初始化程序。

另一种选择是“assert(0)”或在“init”内以另一种方式崩溃,但这不是一个好的解决方案。


0
投票

我实际上投票赞成了 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
,它也应该禁止其后代拥有它。如果它的子类之一想要它回来(这对我来说没有意义),它应该通过调用我指定的初始化程序来显式覆盖它。

但更重要的是,无论您是否采取上述方法,您都应该始终拥有适当的文档。您应该始终清楚地说明哪个方法是您指定的初始化程序,尽最大努力提醒其他人不要在文档中使用不适当的初始化程序,并信任其他用户/开发人员,而不是试图“拯救其他人的屁股”聪明的代码。


0
投票

对于 Swift 5,使用

@available(*, unavailable, message: "This method is not available")
是一种方法,但在这种方法中你不能使用类内部的实例方法。我建议对无法访问的初始化程序使用 private 关键字。

对于继承自 NSObject 的类:

  private override init() {
        super.init()
    }
© www.soinside.com 2019 - 2024. All rights reserved.