是否可以在Objective-C中将-init方法设为私有?

时间:2008-10-12 04:07:41

标签: objective-c

我需要在Objective-C中隐藏(私有)我班级的-init方法。

我该怎么做?

9 个答案:

答案 0 :(得分:328)

NS_UNAVAILABLE

- (instancetype)init NS_UNAVAILABLE;

这是不可用属性的简短版本。它首先出现在macOS 10.7iOS 5中。它在NSObjCRuntime.h中定义为#define NS_UNAVAILABLE UNAVAILABLE_ATTRIBUTE

版本disables the method only for Swift clients,而不是ObjC代码:

- (instancetype)init NS_SWIFT_UNAVAILABLE;

unavailable

unavailable属性添加到标头,以便在对init的任何调用中生成编译器错误

-(instancetype) init __attribute__((unavailable("init not available")));  

compile time error

如果您没有理由,只需输入__attribute__((unavailable)),甚至__unavailable

-(instancetype) __unavailable init;  

doesNotRecognizeSelector:

使用doesNotRecognizeSelector:引发NSInvalidArgumentException。 “只要对象收到无法响应或转发的aSelector消息,运行时系统就会调用此方法。”

- (instancetype) init {
    [self release];
    [super doesNotRecognizeSelector:_cmd];
    return nil;
}

NSAssert

使用NSAssert抛出NSInternalInconsistencyException并显示消息:

- (instancetype) init {
    [self release];
    NSAssert(false,@"unavailable, use initWithBlah: instead");
    return nil;
}

raise:format:

使用raise:format:抛出自己的异常:

- (instancetype) init {
    [self release];
    [NSException raise:NSGenericException 
                format:@"Disabled. Use +[[%@ alloc] %@] instead",
                       NSStringFromClass([self class]),
                       NSStringFromSelector(@selector(initWithStateDictionary:))];
    return nil;
}

[self release]是必需的,因为该对象已经alloc了。使用ARC时,编译器会为您调用它。无论如何,当你打算故意停止执行时,不要担心。

objc_designated_initializer

如果您打算禁用init以强制使用指定的初始化程序,则有一个属性:

-(instancetype)myOwnInit NS_DESIGNATED_INITIALIZER;

除非任何其他初始化方法在内部调用myOwnInit,否则会生成警告。详细信息将在下一个Xcode发布后发布在Adopting Modern Objective-C(我猜)。

答案 1 :(得分:100)

Apple已开始在其头文件中使用以下内容来禁用init构造函数:

- (instancetype)init NS_UNAVAILABLE;

这在Xcode中正确显示为编译器错误。具体来说,这是在他们的几个HealthKit头文件中设置的(HKUnit就是其中之一)。

答案 2 :(得分:85)

与Smalltalk一样,Objective-C没有“私有”与“公共”方法的概念。任何消息都可以随时发送到任何对象。

如果调用NSInternalInconsistencyException方法,您可以执行-init

- (id)init {
    [self release];
    @throw [NSException exceptionWithName:NSInternalInconsistencyException
                                   reason:@"-init is not a valid initializer for the class Foo"
                                 userInfo:nil];
    return nil;
}

另一种选择 - 在实践中可能要好得多 - 就是让-init尽可能为你的班级做点事情。

如果您尝试执行此操作是因为您尝试“确保”使用单个对象,请不要打扰。具体而言,请勿使用“覆盖+allocWithZone:-init-retain-release”创建单例的方法。它实际上总是没有必要,只是增加并发症而没有真正的显着优势。

相反,只需编写代码,使得+sharedWhatever方法是访问单例的方法,并将其记录为在标题中获取单例实例的方法。在绝大多数情况下,这应该是你所需要的。

答案 3 :(得分:3)

如果您正在谈论默认的-init方法,那么您不能。它继承自NSObject,每个类都会响应它而没有任何警告。

您可以创建一个新方法,比如-initMyClass,并将其放在像Matt建议的私有类别中。然后定义默认的-init方法,以便在调用异常时引发异常,或者(更好地)使用某些默认值调用私有-initMyClass。

人们似乎想隐藏init的主要原因之一是singleton objects。如果是这种情况,那么你不需要隐藏-init,只需返回单例对象(或者如果它还不存在则创建它)。

答案 4 :(得分:3)

将其放在头文件

- (id)init UNAVAILABLE_ATTRIBUTE;

答案 5 :(得分:2)

为什么你不能使它“私有/不可见”的问题是因为init方法得到发送到id(因为alloc返回一个id)而不是你的YourClass

请注意,从编译器(checker)的角度来看,id可能会对任何曾经键入的内容做出反应(它无法检查运行时内容中的内容),因此只有在没有任何内容时才能隐藏init( public = in header)使用一个方法init,比编译所知,id没有办法让id响应init,因为在任何地方都没有init(在你的源代码中,所有的库等...)

所以你不能禁止用户传递init并被编译器粉碎......但你可以做的是阻止用户通过调用init来获取真实实例

简单地通过实现init,它返回nil并且有一个(私有/不可见)初始化器,其他人不会得到这个名称(如initOnce,initWithSpecial ......)

static SomeClass * SInstance = nil;

- (id)init
{
    // possibly throw smth. here
    return nil;
}

- (id)initOnce
{
    self = [super init];
    if (self) {
        return self;
    }
    return nil;
}

+ (SomeClass *) shared 
{
    if (nil == SInstance) {
        SInstance = [[SomeClass alloc] initOnce];
    }
    return SInstance;
}

注意:有人可以这样做

SomeClass * c = [[SomeClass alloc] initOnce];

它实际上会返回一个新实例,但如果initOnce在我们的项目中没有公开(在标题中)声明,它会生成一个警告(id可能不会响应......)而且无论如何使用这个,需要确切地知道真正的初始化程序是initOnce

我们可以进一步防止这种情况,但是没有必要

答案 6 :(得分:1)

这取决于你的意思是“私人”。在Objective-C中,调用对象上的方法可能更好地描述为向该对象发送消息。语言中没有任何内容禁止客户端调用对象上的任何给定方法;你能做的最好的事情就是不在头文件中声明方法。如果客户端仍然使用正确的签名调用“私有”方法,它仍将在运行时执行。

也就是说,在Objective-C中创建私有方法的最常用方法是在实现文件中创建Category,并在其中声明所有“隐藏”方法。请记住,这不会真正阻止对init的调用运行,但如果有人试图这样做,编译器会发出警告。

MyClass.m

@interface MyClass (PrivateMethods)
- (NSString*) init;
@end

@implementation MyClass

- (NSString*) init
{
    // code...
}

@end

MacRumors.com上关于这个话题有一个不错的thread

答案 7 :(得分:1)

您可以使用NS_UNAVAILABLE声明任何方法不可用。

所以你可以将这些行放在@interface

之下
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;

更好地在前缀标题中定义一个宏

#define NO_INIT \
- (instancetype)init NS_UNAVAILABLE; \
+ (instancetype)new NS_UNAVAILABLE;

@interface YourClass : NSObject
NO_INIT

// Your properties and messages

@end

答案 8 :(得分:0)

我必须提到,在子类中隐藏方法时放置断言和引发异常对于预期的方法来说有一个令人讨厌的陷阱。

我建议将__unavailable用作Jano explained for his first example

可以在子类中重写方法。这意味着如果超类中的方法使用的方法只是在子类中引发异常,那么它可能无法按预期工作。换句话说,你刚刚打破了过去的工作。初始化方法也是如此。以下是这种相当常见的实现的示例:

- (SuperClass *)initWithParameters:(Type1 *)arg1 optional:(Type2 *)arg2
{
    ...bla bla...
    return self;
}

- (SuperClass *)initWithLessParameters:(Type1 *)arg1
{
    self = [self initWithParameters:arg1 optional:DEFAULT_ARG2];
    return self;
}

想象一下-initWithLessParameters会发生什么,如果我在子类中这样做:

- (SubClass *)initWithParameters:(Type1 *)arg1 optional:(Type2 *)arg2
{
    [self release];
    [super doesNotRecognizeSelector:_cmd];
    return nil;
}

这意味着您应该倾向于使用私有(隐藏)方法,尤其是在初始化方法中,除非您计划重写方法。但是,这是另一个主题,因为您并不总是完全控制超类的实现。 (这让我质疑使用__attribute((objc_designated_initializer))作为不好的做法,尽管我没有深入使用它。)

它还意味着您可以在必须在子类中重写的方法中使用断言和异常。 (Creating an abstract class in Objective-C)中的“抽象”方法

并且,不要忘记+ new class方法。