是否有可能使-init方法在Objective-C中是私有的?

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

我怎样才能做到这一点?

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为你的class级做一些明智的事情。

如果你正在尝试这样做,因为你正在试图“确保”使用单例对象,请不要打扰。 具体来说,不要打扰创build单例的“override +allocWithZone: ,- +allocWithZone:-retain-release ”方法。 这几乎总是不必要的,只是增加了复杂性,没有真正的显着优势。

相反,只要编写你的代码,使你的+sharedWhatever方法是你如何访问一个单例,并将其作为在头文件中获取单例实例的方式。 在绝大多数情况下,这应该是你所需要的。

unavailable

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

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

编译时错误

如果您没有理由,只需键入__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:抛出你自己的exception:

 - (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内部调用myOwnInit 。 详细信息将在下一个Xcode发布之后(我猜)在采用现代Objective-C中发布。

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

 - (instancetype)init NS_UNAVAILABLE; 

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

如果你正在谈论默认的-init方法,那么你不能。 它是从NSObjectinheritance而来的,每个类都会响应,而不会有任何警告。

你可以创build一个新的方法,比如-initMyClass,并把它放在一个私人的类别,如马特build议的。 然后定义默认的-init方法,如果它被调用,或者(更好地)用一些默认值调用你的私有-initMyClass,则引发exception。

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

把它放在头文件中

 - (id)init UNAVAILABLE_ATTRIBUTE; 

以及为什么你不能让它“私人/不可见”的问题是因为init方法获取发送到id(作为alloc返回一个id)不YourClass

请注意,从编译器(检查器)的angular度来看,一个id可以对任何types的事件作出响应(它不能检查运行时真正的id),所以只有在没有任何地方会隐藏init的时候(public = in头)使用一个方法init,比编译会知道,没有办法让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

我们可以进一步防止这一点,但没有必要

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

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

MyClass.m

 @interface MyClass (PrivateMethods) - (NSString*) init; @end @implementation MyClass - (NSString*) init { // code... } @end 

MacRumors.com上有一个关于这个话题的正式线索 。

我不得不提到,把断言和提出例外隐藏在子类中的方法对于良好的意图来说是一个讨厌的陷阱。

我会build议使用__unavailable作为__unavailable 解释他的第一个例子 。

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

 - (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))的使用是不好的做法,虽然我没有深入使用它。)

这也意味着你可以在必须在子类中重写的方法中使用断言和exception。 ( 在Objective-C中创build抽象类的“抽象”方法)

而且,不要忘记+新的类方法。

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

所以你可以把这些行放在你的@interface下面

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

甚至更好的在你的前缀头文件中定义一个macros

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

 @interface YourClass : NSObject NO_INIT // Your properties and messages @end