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

发布于 2024-12-27 15:05:50 字数 157 浏览 0 评论 0原文

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

我怎样才能做到这一点?

I want to force user to use my own init method (for example -(id)initWithString:(NSString*)foo;) and not the basic [[myObject alloc]init];.

how can I do that?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

鸠魁 2025-01-03 15:05:50

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

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

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

All other answers here are outdated. There is a way to do this properly now!

While it is easy to just crash at runtime when somebody calls your method, compile-time checking would be far preferable.

Fortunately, this has been possible in Objective-C for a while.

Using LLVM, you can declare any method as unavailable in a class like so

- (void)aMethod __attribute__((unavailable("This method is not available")));

This will make the compiler complain when trying to call aMethod. Great!

Since - (id)init is just an ordinary method, you can prohibit calling of the default (or any other) initializer in this way.

Note, though, that this will not insure against the method being called using the dynamic aspects of the language, for instance via [object performSelector:@selector(aMethod)] etc. In the case of init, you won't even get a warning, because the init method is defined in other classes, and the compiler doesn't know enough to give you an undeclared selector warning.

So, to ensure against this, make sure that the init method crashes when being called (see Adam's answer).

If you want to disallow - (id)init in a framework, make sure to also disallow + (id)new, as this will just forward to init.

Javi Soto has written a small macro to forbid using the designated initializer faster and easier and to give nicer messages. You can find it here.

清晨说晚安 2025-01-03 15:05:50

TL; dr

Swift

private init() {}

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

目标 C:

将其放入类的 .h 文件中。

- (instancetype)init NS_UNAVAILABLE;

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

tl; dr

Swift:

private init() {}

Since all Swift classes include an internal init by default, you can change it to private to keep other classes from calling it.

Objective C:

Put this in your class's .h file.

- (instancetype)init NS_UNAVAILABLE;

This relies on an OS define that prevents the method named from being called.

懵少女 2025-01-03 15:05:50

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

您有一个名为“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]”是实例化的类 - 当 SUBCLASS 为调用 [super init],或者当使用普通 [[SuperClass alloc] init] 调用 SUPERCLASS 时将是 SUPERCLASS
  4. 因此,当超类收到“init”调用时,它只需要检查是否alloc'd 类与其自己的类相同,

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

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

The accepted answer is incorrect - you CAN do this, and it's very easy, you just have to be a bit explicit. Here's an example:

You have a class named "DontAllowInit" which you want to prevent people init'ing:

@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;
}

Explanation:

  1. When you call [super init], the class that was alloc'd was the SUBCLASS.
  2. "self" is the instance - i.e. the thing that was init'd
  3. "[self class]" is the class that was instantiated - which will be SUBCLASS when the SUBCLASS is calling [super init], or will be the SUPERCLASS when the SUPERCLASS is being called with plain [[SuperClass alloc] init]
  4. So, when the superclass receives an "init" call, it just needs to check whether the alloc'd class is the same as its own class

Works perfectly. NB: I don't recommend this technique for "normal apps" because usually you INSTEAD want to use a Protocol.

HOWEVER ... when writing Libraries ... this technique is VERY valuable: you frequently want to "save (other developers) from themselves", and its easy to NSAssert and tell them "Oops! you tried to alloc/init the wrong class! Try class X instead...".

贩梦商人 2025-01-03 15:05:50
-(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: @""];
}
-(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

Throwing the exception is justified if you document that -init is not allowed and therefore using it is a programmer error. However, a better answer would be to make -init invoke -initWtihString: with some suitable default value i.e.

-(id) init
{
    return [self initWithString: @""];
}
找个人就嫁了吧 2025-01-03 15:05:50

简短的回答:你不能。

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

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

Short answer: you can't.

Longer answer: the best practice is to set your most detailed initializer as the designated initializer, as described here. 'init' will then call that initializer with sane, default values.

Another option is to 'assert(0)' or crash in another way inside the 'init', but this isn't a good solution.

影子是时光的心 2025-01-03 15:05:50

实际上我投票赞成了亚当的答案,但想添加一些内容。

首先,强烈建议您检查 selfnil<(如 NSObject 子类中自动生成的 init 方法所示)。 /code> 在 init 中。另外,我不认为类对象保证像 == 中那样“相等”。我这样做更像是

- (id)init
{
    NSAssert(NO, @"You are doing it wrong.");
    self = [super init];
    if ([self isKindOfClass:[InitNotAllowedClass class]])
        self = nil;
    return self;
}

注意,我使用 isKindOfClass: 代替,因为恕我直言,如果此类不允许 init,它也应该不允许其后代拥有它。如果它的子类之一想要它回来(这对我来说没有意义),它应该通过调用我指定的初始值设定项来显式覆盖它。

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

I actually voted up Adam's answer, but would like to add some things to it.

First, it is strongly encouraged (as seem in auto-generated init methods in NSObject subclasses) that you check self against nil in inits. Also, I don't think class objects are guaranteed to be "equal" as in ==. I do this more like

- (id)init
{
    NSAssert(NO, @"You are doing it wrong.");
    self = [super init];
    if ([self isKindOfClass:[InitNotAllowedClass class]])
        self = nil;
    return self;
}

Note that I use isKindOfClass: instead because IMHO if this class disallows init, it should disallow its descendants to have it as well. If one of its subclass want it back (which doesn't make sense for me), it should override it explicitly by calling my designated initializer.

But more importantly, whether you take the above approach or not, you should always have appropriate documentation. You should always clearly state which method is your designated initializer, try as best as you can to remind others not to use inappropriate initializers in documentation, and put some faith in other users/developers, instead of trying to "save everybody else's asses" with clever codes.

下雨或天晴 2025-01-03 15:05:50

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

对于继承自 NSObject 的类:

  private override init() {
        super.init()
    }

For Swift 5, using @available(*, unavailable, message: "This method is not available") is a way but in this approach you can't use the instance method inside the class. I suggest using private keyword for inaccesible initializers.

For a class that inherits from NSObject:

  private override init() {
        super.init()
    }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文