Answers:
与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方法成为访问单例的方式,并将其记录为在标头中获取单例实例的方式。在绝大多数情况下,这就是您所需要的。
alloc并init使其代码功能不正确,因为它们具有正确的类,但是实例错误。这是OO 中封装原理的本质。您在API中隐藏了其他类不需要或无法访问的东西。您不仅要将所有内容都公开,还希望人类能够对其进行跟踪。
NS_UNAVAILABLE- (instancetype)init NS_UNAVAILABLE;
这是不可用属性的简短版本。它首先出现在macOS 10.7和iOS 5中。在NSObjCRuntime.h中将其定义为#define NS_UNAVAILABLE UNAVAILABLE_ATTRIBUTE。
有一个版本仅对Swift客户端禁用此方法,而不对ObjC代码禁用:
- (instancetype)init NS_SWIFT_UNAVAILABLE;
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:抛出自己的异常:
- (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发布后,详细信息将发布在“ 采用现代Objective-C”中(我想)。
init。因为,如果此方法无效,那么为什么要初始化一个对象?另外,抛出异常时,您可以指定一些自定义消息,将正确的init*方法传达给开发人员,而在情况下则没有这种选择doesNotRecognizeSelector。
Apple已开始在其头文件中使用以下命令禁用init构造函数:
- (instancetype)init NS_UNAVAILABLE;
这在Xcode中正确显示为编译器错误。具体来说,这是在其多个HealthKit头文件中设置的(HKUnit是其中之一)。
如果您正在谈论默认的-init方法,那么您就不能这样做。它是从NSObject继承的,每个类都将对其进行响应而不会发出警告。
您可以创建一个新方法,例如-initMyClass,并将其放在Matt所建议的私有类别中。然后定义默认的-init方法以引发异常(如果已调用)或(更好)使用一些默认值调用您的私有-initMyClass。
人们似乎想要隐藏init的主要原因之一是单例对象。如果是这种情况,则无需隐藏-init,只需返回单例对象(或在尚不存在时创建它)。
把它放在头文件中
- (id)init UNAVAILABLE_ATTRIBUTE;
您可以使用声明不可用的任何方法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
这取决于您“私有化”的意思。在Objective-C中,在对象上调用方法可能更好地描述为向该对象发送消息。语言中没有什么可以阻止客户端调用对象上的任何给定方法。最好的办法是不要在头文件中声明该方法。但是,如果客户端使用正确的签名调用“私有”方法,它仍将在运行时执行。
也就是说,在Objective-C中创建私有方法的最常见方法是在实现文件中创建Category,然后在其中声明所有“隐藏”方法。请记住,这并不能真正阻止调用的init运行,但是如果有人尝试执行此操作,则编译器会发出警告。
MyClass.m
@interface MyClass (PrivateMethods)
- (NSString*) init;
@end
@implementation MyClass
- (NSString*) init
{
// code...
}
@end
有一个像样的线程有关此主题的MacRumors.com。
好了,为什么不能将其设为“私有/不可见”的问题是导致init方法发送给id(因为alloc返回一个id)而不是YourClass
请注意,从编译器(检查器)的角度来看,一个id可能会对曾经键入的任何内容做出响应(它无法在运行时检查该id中真正包含的内容),因此您只能在无处可寻的情况下隐藏init(public = in标头)使用一种方法init(比编译会知道的那样),因为id在任何地方都没有init(在您的源代码中,所有的lib等等),所以没有办法响应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
我们可以进一步防止这种情况,但是没有必要
我不得不提到,在子类中放置断言和引发异常以隐藏方法会给善意的人带来麻烦。
我会建议使用__unavailable作为Jano的他的第一个例子来说明。
方法可以在子类中覆盖。这意味着,如果超类中的方法使用的方法仅在子类中引发异常,则该方法可能无法按预期工作。换句话说,您已经打破了以前的工作方式。初始化方法也是如此。这是这种相当常见的实现的示例:
- (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))用作不好的做法,尽管我没有深入使用它。)
它还暗示您可以在必须在子类中覆盖的方法中使用断言和异常。(如在Objective-C中创建抽象类中的“抽象”方法)
并且,不要忘记+ new class方法。
NS_UNAVAILABLE。我通常会敦促您使用这种方法。OP会考虑修改他们接受的答案吗?这里的其他答案提供了许多有用的细节,但不是实现此目的的首选方法。