Is it possible to make the -init method private in Objective-C?
我需要把我班的
我该怎么做?
1 | - (instancetype)init NS_UNAVAILABLE; |
这是不可用属性的简短版本。它首次出现在MacOS 10.7和iOS 5中。它在nsobjcruntime.h中定义为
有一个版本仅对Swift客户机禁用该方法,而不是对objc代码禁用该方法:
1 | - (instancetype)init NS_SWIFT_UNAVAILABLE; |
将
1 | -(instancetype) init __attribute__((unavailable("init not available"))); |
如果您没有理由,只需键入
1 | -(instancetype) __unavailable init; |
使用
1 2 3 4 5 | - (instancetype) init { [self release]; [super doesNotRecognizeSelector:_cmd]; return nil; } |
使用
1 2 3 4 5 | - (instancetype) init { [self release]; NSAssert(false,@"unavailable, use initWithBlah: instead"); return nil; } |
使用
1 2 3 4 5 6 7 8 | - (instancetype) init { [self release]; [NSException raise:NSGenericException format:@"Disabled. Use +[[%@ alloc] %@] instead", NSStringFromClass([self class]), NSStringFromSelector(@selector(initWithStateDictionary:))]; return nil; } |
需要
如果您打算禁用
1 | -(instancetype)myOwnInit NS_DESIGNATED_INITIALIZER; |
除非任何其他初始值设定项方法在内部调用
Apple已开始在其头文件中使用以下内容来禁用init构造函数:
1 | - (instancetype)init NS_UNAVAILABLE; |
这在Xcode中正确显示为编译器错误。具体来说,这是在他们的几个healthkit头文件中设置的(hkUnit就是其中之一)。
与Smalltalk一样,Objective-C没有"私有"和"公共"方法的概念。任何消息都可以随时发送到任何对象。
如果调用了
1 2 3 4 5 6 7 | - (id)init { [self release]; @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"-init is not a valid initializer for the class Foo" userInfo:nil]; return nil; } |
另一种选择——在实践中可能要好得多——是让
如果您试图这样做是因为您试图"确保"使用一个单例对象,不要费心。具体来说,不必费心用"override
相反,只需编写代码,使您的
把这个放在头文件里
1 | - (id)init UNAVAILABLE_ATTRIBUTE; |
如果您谈论的是默认的-init方法,那么您就不能这样做了。它是从nsObject继承的,并且每个类都不会对它做出任何警告。
您可以创建一个新的方法,比如-initmyclass,并将其放入Matt建议的私有类别中。然后定义default-init方法,以便在调用它时引发异常,或者(更好)使用一些默认值调用私有的-initmyclass。
人们希望隐藏init的主要原因之一是针对singleton对象。如果是这种情况,那么您不需要隐藏-init,只需返回singleton对象即可(如果尚不存在则创建它)。
那么,为什么不能将其设为"私有/不可见"的问题是因为init方法被发送到id(因为alloc返回一个id)而不是您的类
注意,从编译器(checker)的角度来看,一个ID可能会对任何输入的内容作出响应(它不能在运行时检查真正进入ID的内容),所以只有当没有任何地方(公开=在头中)会使用方法in it时,您才可以隐藏init,因为在任何地方都不是init(在源代码、所有libs等中)
所以不能禁止用户通过init并被编译器破坏…但是,您可以做的是,通过调用init来防止用户获得真正的实例。
只需实现init,它返回nil并具有(private/invisible)初始值设定项,该初始值设定项将命名其他人无法获取的名称(如initonce、initWithSpecial…)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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; } |
注意:有人可以这样做
1 | SomeClass * c = [[SomeClass alloc] initOnce]; |
它实际上会返回一个新的实例,但是如果initonce在我们的项目中不会公开(在头中)声明,它将生成一个警告(id可能不会响应…)并且无论如何,使用这个的人需要确切地知道真正的初始值设定项是initonce
我们可以进一步阻止这种情况,但没有必要
您可以使用
所以您可以将这些行放在@interface下面
1 2 | - (instancetype)init NS_UNAVAILABLE; + (instancetype)new NS_UNAVAILABLE; |
最好在前缀头中定义一个宏
1 2 3 | #define NO_INIT \ - (instancetype)init NS_UNAVAILABLE; \ + (instancetype)new NS_UNAVAILABLE; |
和
1 2 3 4 5 6 | @interface YourClass : NSObject NO_INIT // Your properties and messages @end |
这取决于你所说的"私人化"。在Objective-C中,对对象调用方法最好描述为向该对象发送消息。语言中没有禁止客户端对对象调用任何给定方法的内容;最好不要在头文件中声明该方法。如果客户机仍然使用正确的签名调用"private"方法,它仍将在运行时执行。
也就是说,在Objective-C中创建私有方法的最常见方法是在实现文件中创建一个类别,并声明其中所有的"隐藏"方法。记住,这不会真正阻止对
MyClass
1 2 3 4 5 6 7 8 9 10 11 12 | @interface MyClass (PrivateMethods) - (NSString*) init; @end @implementation MyClass - (NSString*) init { // code... } @end |
关于这个话题,在macrumers.com上有一个不错的话题。
我必须提到,在子类中放置断言和引发异常以隐藏方法,对于计划良好的方法有一个令人讨厌的陷阱。
我建议使用
方法可以在子类中重写。这意味着,如果超类中的方法使用了一个只在子类中引发异常的方法,那么它可能无法按预期工作。换句话说,你刚刚打破了过去的工作模式。初始化方法也是如此。下面是这样一个非常常见的实现示例:
1 2 3 4 5 6 7 8 9 10 11 | - (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会发生什么:
1 2 3 4 5 6 | - (SubClass *)initWithParameters:(Type1 *)arg1 optional:(Type2 *)arg2 { [self release]; [super doesNotRecognizeSelector:_cmd]; return nil; } |
这意味着您应该倾向于使用私有(隐藏)方法,尤其是在初始化方法中,除非您计划重写这些方法。但是,这是另一个主题,因为您并不总是完全控制超类的实现。(这让我怀疑使用uu属性((objc_指定的初始值设定项))作为不好的实践,尽管我没有深入使用它。)
它还意味着您可以在必须在子类中重写的方法中使用断言和异常。(在Objective-C中创建抽象类的"抽象"方法)
而且,不要忘记+新的类方法。