可以在Objective-C中使-init方法私有吗?

可以在Objective-C中使-init方法私有吗?,objective-c,Objective C,我需要在Objective-C中隐藏类的-init方法 我该怎么做呢?这取决于你说的“保密”是什么意思。在Objective-C中,对对象调用方法最好描述为向该对象发送消息。语言中没有任何东西禁止客户端对对象调用任何给定的方法;最好不要在头文件中声明方法。如果客户机仍然使用正确的签名调用私有方法,它仍将在运行时执行 也就是说,在Objective-C中创建私有方法最常见的方法是在实现文件中创建一个,并在其中声明所有隐藏的方法。请记住,这并不会真正阻止对init的调用运行,但如果有人试图这样做,编

我需要在Objective-C中隐藏类的-init方法


我该怎么做呢?

这取决于你说的“保密”是什么意思。在Objective-C中,对对象调用方法最好描述为向该对象发送消息。语言中没有任何东西禁止客户端对对象调用任何给定的方法;最好不要在头文件中声明方法。如果客户机仍然使用正确的签名调用私有方法,它仍将在运行时执行

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

我的班级

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

@implementation MyClass

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

@end

MacRumors.com上有一个关于这个话题的不错的例子。

如果你在谈论默认的init方法,那么你就不能。它是从NSObject继承的,每个类都会响应它而不发出警告

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


人们想要隐藏init的一个主要原因是。如果是这种情况,那么您不需要隐藏-init,只需返回singleton对象,或者在它还不存在的情况下创建它。

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

如果调用了-init方法,则可以引发NSInternalInconsistencyException:

- (id)init {
    [self release];
    @throw [NSException exceptionWithName:NSInternalInconsistencyException
                                   reason:@"-init is not a valid initializer for the class Foo"
                                 userInfo:nil];
    return nil;
}
另一种选择——在实践中可能更好——是尽可能让-init为您的类做一些合理的事情

如果您试图这样做是因为您试图确保使用单例对象,那么不要麻烦了。具体地说,不要使用override+allocWithZone:,-init,-retain,-release方法来创建单例。它实际上总是不必要的,只是增加了复杂性,没有真正的显著优势

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

NS\u不可用 这是不可用属性的简短版本。它最早出现在macOS和。它在NSObjCRuntime.h中定义为define NS_UNAVAILABLE_属性

有一个版本不适用于ObjC代码:

- (instancetype)init NS_SWIFT_UNAVAILABLE;
不可用的 将该属性添加到标头,以便在调用init时生成编译器错误

-(instancetype) init __attribute__((unavailable("init not available")));  
如果没有原因,只需键入_属性_不可用,甚至_不可用:

不识别选择器: 用于引发NSInvalidArgumentException。“每当对象收到无法响应或转发的aSelector消息时,运行时系统就会调用此方法。”

恩萨塞特 用于引发NSInternalInconsistencyException并显示消息:

- (instancetype) init {
    [self release];
    NSAssert(false,@"unavailable, use initWithBlah: instead");
    return nil;
}
提高:格式: 用于引发您自己的异常:

- (instancetype) init {
    [self release];
    [NSException raise:NSGenericException 
                format:@"Disabled. Use +[[%@ alloc] %@] instead",
                       NSStringFromClass([self class]),
                       NSStringFromSelector(@selector(initWithStateDictionary:))];
    return nil;
}
需要[self release],因为对象已分配。使用ARC时,编译器将为您调用它。在任何情况下,当你打算故意停止执行时,不要担心

objc_指定_初始值设定项 如果您打算禁用init以强制使用指定的初始值设定项,则有一个属性:

-(instancetype)myOwnInit NS_DESIGNATED_INITIALIZER;

这将生成警告,除非任何其他初始值设定项方法在内部调用myOwnInit。详细信息将在下一个Xcode发行版后发布。

为什么不能将其设置为私有/不可见的问题是,由于alloc返回的id不是您的类,因此init方法会被发送到id

请注意,从编译器检查器的角度来看,id可能会响应任何键入的内容,但它无法在运行时检查id中真正包含的内容,因此,只有当没有任何地方会使用方法init时,才可以隐藏init,编译人员会知道,id无法响应init,由于源代码中没有init,所以所有lib等

因此,您不能禁止用户通过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


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

我必须提到,在子类中放置断言和引发异常以隐藏方法对于良好的意图来说是一个令人讨厌的陷阱

我建议使用不可用的作为

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

- (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;
}
这意味着您应该倾向于使用私有隐藏方法,尤其是在初始化方法中,除非您计划重写这些方法。但是,这是另一个主题,因为您并不总是能够完全控制超类的实现。这使我怀疑使用_attributeobjc_指定的_初始值设定项是一种不好的做法,尽管我还没有深入使用它

它还意味着您可以在必须在子类中重写的方法中使用断言和异常。中的抽象方法


另外,不要忘记+new class方法。

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

- (instancetype)init NS_UNAVAILABLE;

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

将其放在头文件中

- (id)init UNAVAILABLE_ATTRIBUTE;

您可以使用NS\u UNAVAILABLE将任何方法声明为不可用

因此,您可以将这些行放在@interface下面

- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
最好在前缀头中定义宏

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



不幸的是,在这种情况下,分类方法并没有真正的帮助。通常,它会向您购买编译时警告,说明该方法可能未在类上定义。但是,由于MyClass必须从一个根类继承,并且它们定义了init,因此不会有任何警告。这里是否确实需要返回?是的,为了让编译器满意。否则编译器可能会抱怨非void返回的方法没有返回。有趣的是,我没有。可能是不同的编译器版本或开关?我只是使用默认的gcc开关和XCode 3.1计算开发人员遵循的模式不是一个好主意。最好抛出一个异常,这样不同团队中的开发人员就知道不要抛出异常。我认为私人概念会更好。因为没有真正的显著优势。完全不真实。显著的优点是您希望强制执行单例模式。如果您允许创建新实例,那么不熟悉API的开发人员可能会使用alloc和init,并错误地使用其代码函数,因为它们的类是正确的,但实例是错误的。这就是OO中封装原则的本质。您在API中隐藏了其他类不需要或无法访问的内容。你不只是把每件事都公之于众,并期望人们跟踪每件事。这似乎是一种更好的方法,而不仅仅是把“init”单独留在你的单例中,依靠文档与用户交流,他们应该通过“sharedWhatever”访问这些文档。人们通常在浪费了很多时间试图找出问题之前不会阅读文档。这对于init以外的方法来说是很好的。因为,如果此方法无效,为什么要初始化对象?另外,当抛出异常时,您将能够指定一些自定义消息,将正确的init*方法传递给开发人员,而在DoesNotReconficateSelector的情况下,您没有这样的选项。不知道Aleks,这不应该存在:我编辑了答案。这很奇怪,因为它最终导致系统崩溃。我想最好不要让事情发生,但我想知道是否有更好的办法。我希望其他开发人员在“运行”或“构建”时不能调用它并让它被编译器捕获。我尝试了这个方法,但它不起作用:-id init\uuuuu attribute\uuu unavailableinit not available{NSAssertfalse,@Use initWithType;return nil;}@miraj听起来您的编译器不支持它。它在Xcode 6中受支持。如果一个初始值设定项没有调用指定的初始值设定项,您应该得到“便利初始值设定项缺少对另一个初始值设定项的‘self’调用”;您还可以执行+instancetypenew NS\u UNAVAILABLE@sonicfly曾尝试过这样做,但该项目仍在编译。不推荐这样做。苹果的现代objective-c文档声明init应该返回instancetype,而不是id。它是:-instancetype init NS\u不可用;现在有一个特定的、干净的、描述性的工具来实现这一点,如中所示。特别是:不可用。我会
我通常敦促你使用这种方法。OP会考虑修改他们所接受的答案吗?这里的其他答案提供了很多有用的细节,但不是实现这一点的首选方法。正如下面其他人所指出的,NS_UNAVAILABLE仍然允许调用方通过new间接调用init。简单地重写init以返回nil将处理这两种情况。当然,您可以使“new”NS_和“init”不可用,这是目前的常见做法。
- (id)init UNAVAILABLE_ATTRIBUTE;
- (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