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

时间:2012-01-18 09:25:10

标签: ios objective-c nsobject

我想强制用户使用我自己的init方法(例如-(id)initWithString:(NSString*)foo;)而不是基本的[[myObject alloc]init];

我该怎么做?

6 个答案:

答案 0 :(得分:20)

此处的所有其他答案都已过时。有一种方法可以正确地做到这一点!

虽然在某人调用您的方法时很容易在运行时崩溃,但编译时检查将更为可取。

幸运的是,在Objective-C中已经有一段时间了。

使用LLVM,您可以在类似

的类中声明任何方法不可用
- (void)aMethod __attribute__((unavailable("This method is not available")));

这会使编译器在尝试调用aMethod时抱怨。太好了!

由于- (id)init只是一种普通方法,因此您可以禁止以这种方式调用默认(或任何其他)初始值设定项。

但请注意,这不会保证使用该语言的动态方面来调用该方法,例如通过[object performSelector:@selector(aMethod)]等。在init的情况下,您赢了甚至得到一个警告,因为init方法是在其他类中定义的,编译器不知道给你一个未声明的选择器警告。

因此,为了确保这一点,请确保在调用时init方法崩溃(请参阅Adam's answer)。

如果您想在框架中禁止- (id)init,请务必同时禁止使用+ (id)new,因为这只会转发给初始化。

Javi Soto编写了一个小宏来禁止使用指定的初始化程序更快更容易并提供更好的消息。你可以找到它here

答案 1 :(得分:12)

TL;医生

<强>夫特

private init() {}

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

目标C:

将它放在班级的.h文件中。

- (instancetype)init NS_UNAVAILABLE;

这依赖于一个操作系统定义,它阻止了被调用的方法。

答案 2 :(得分: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”是实例 - 即init'd
  3. “[self class]”是实例化的类 - 当SUBCLASS调用[super init]时它将是SUBCLASS,或者当用普通调用SUPERCLASS时它将是SUPERCLASS [[SuperClass alloc] init]
  4. 因此,当超类收到“init”调用时,只需检查 alloc'd 类是否与其自己的类相同
  5. 完美无缺。注意:我不推荐这种技术用于“普通应用程序”,因为通常你INSTEAD想要使用协议。

    但是......在编写库时...这种技术非常有价值:你经常想“自己保存(其他开发人员)”,并且易于NSAssert并告诉他们“哎呀!你试过alloc / init错误的类!尝试使用X类......“。

答案 3 :(得分: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: @""];
}

答案 4 :(得分:0)

我实际上投了Adam的答案,但想补充一些东西。

首先,强烈建议您(init子类中的自动生成的NSObject方法)在self nilinit查看==秒。另外,我认为类对象不能保证与- (id)init { NSAssert(NO, @"You are doing it wrong."); self = [super init]; if ([self isKindOfClass:[InitNotAllowedClass class]]) self = nil; return self; } 中的“相等”。我这样做更像是

isKindOfClass:

请注意,我使用init代替因为恕我直言,如果此类不允许{{1}},它应该禁止其后代也拥有它。如果它的一个子类想要它(这对我来说没有意义),它应该通过调用我指定的初始化器显式覆盖它。

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

答案 5 :(得分:-1)

简短回答:你不能。

更长的答案:最佳做法是将最详细的初始化程序设置为指定的初始值设定项,如here所述。然后'init'将使用合理的默认值调用该初始化程序。

另一种选择是'断言(0)'或在'init'中以另一种方式崩溃,但这不是一个好的解决方案。