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

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

我该怎么做?

43839 次浏览

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

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

MyClass.m

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


@implementation MyClass


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


@end

MacRumors.com 上有个不错的 线关于这个话题。

如果您讨论的是 default-init 方法,那么您就不能。它是从 NSObject 继承而来的,每个类都将在没有警告的情况下响应它。

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

人们想要隐藏 init 的主要原因之一是为了 单一对象。如果是这种情况,那么您就不需要 hide-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为你的课堂做一些明智的事情,这在实践中可能要好得多。

如果您试图这样做是因为您试图“确保”一个单例对象被使用,那么不必麻烦了。特别是,不要使用“覆盖 +allocWithZone:-init-retain-release”方法来创建单例。它实际上总是没有必要的,只是增加了复杂性,没有真正的重大优势。

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

NS_UNAVAILABLE

- (instancetype)init NS_UNAVAILABLE;

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

有一个版本是 仅对 Swift 客户端禁用该方法,而不是对象代码:

- (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

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

NSAssert

使用 NSAssert抛出 NSInternalInconcencyException 并显示一条消息:

- (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]是因为对象已经 allocated 了。当使用 ARC 时,编译器会为您调用它。无论如何,当您准备故意停止执行时,不必担心。

objc_designated_initializer

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

-(instancetype)myOwnInit NS_DESIGNATED_INITIALIZER;

除非任何其他初始值设定项方法在内部调用 myOwnInit,否则将生成警告。详细信息将在下一个 Xcode 版本(我猜)之后在 采用现代目标 -C中发布。

那么,为什么不能将其设置为“私有/不可见”的问题是因为 init 方法被发送到 id (因为 alloc 返回一个 id)而不是 YourClass

请注意,从编译器(Checker)的角度来看,id 可能会对任何输入的内容做出响应(它无法在运行时检查到底输入到 id 中的内容) ,所以只有在没有任何地方(public = in header)使用 init 方法时,你才可以隐藏 init,而编译器会知道,id 没有办法响应 init,因为在任何地方(在源代码中,所有的 libs 中等等)都没有 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作为 哈诺解释了他的第一个例子

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

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


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

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

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

这意味着您应该倾向于使用私有(隐藏)方法,特别是在初始化方法中,除非您计划重写这些方法。但是,这是另一个主题,因为您并不总是能够完全控制超类的实现。(这让我怀疑使用 _ _ tribute ((objecc _ ) _ initializer)是否是一种不好的做法,尽管我还没有深入地使用它。)

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

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

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

- (instancetype)init NS_UNAVAILABLE;

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

把这个放在头文件

- (id)init UNAVAILABLE_ATTRIBUTE;

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

所以你可以把这些线放在你的@接口下面

- (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