0

可能重复:
为什么 Objective-C 不支持私有方法?
私有方法声明 Objective-C

我想做的是将Objective C中特定方法的实现设为私有(使用Java术语)。也就是说,类可以根据需要调用的方法,而不必担心该方法会被子类覆盖。也许这个词是“安全的,不会被覆盖”。

例如,一个对象有多个 init 方法,并且有一个私有的 initialize 方法来进行常见的初始化:

- (void) initWithFile:(NSString *)file
{
    if ((self = [super init]) != nil) {
        self.file = file;
        self.url = nil;
        [self initialize]
    }
    return self;
}

- (void) initWithURL:(NSURL *)url
{
    if ((self = [super init]) != nil) {
        self.url = url;
        self.file = nil;
        [self initialize]
    }
    return self;
}

- (void) initialize
{
    ...
}

这是可行的,如果 initialize 没有在 .h 文件中声明,它就会被“保护”而不会被其他类调用。

我遇到了子类的问题(假设以下类是上一个示例的子类):

- (void) initWithFile:(NSString *)file
{
    if ((self = [super initWithFile:file]) != nil) {
        [self initialize];   // my local initializations
    }
    return self;
}

- (void) initialize
{
    ... // my private stuff
}

问题是在这个例子中,基类的initialize方法现在从未被调用,而子类的initialize方法被调用了两次,因为子类的initialize方法覆盖了基类的方法。

我知道我可以在子类中调用 [super initialize],并在子类的 init 方法中省略调用,但这需要子类知道基类的内部结构。由于子类不需要访问基类的源代码,并且只需要 .h 接口文件,因此完全有理由认为它可以实现初始化方法而不会导致改变行为的不良副作用的基类。

我之所以问这个问题是因为我遇到了一个与未在初始化函数中设置的属性直接相关的问题,只是发现它被子类篡夺了,我花了一些时间来调试它。我想首先防止这种情况发生。

4

6 回答 6

1

Objective-C 中没有这样的东西。你可以做的只是不定义方法。

苹果经常这样做。

@interface SomeClass ()
- (void)_someNonPublicMethod;
@end

这是一个类别,它简单地说应该在类中定义什么。您可以在.m文件中执行此操作。

就像已经提到的其他一些答案一样,您可以使用 class-dump 找出类实现了哪些方法,然后您可以覆盖这些方法,即使它们是私有的。

于 2012-12-22T18:30:34.887 回答
1

objC 中的方法没有 private 或 final 关键字

只是不要将它们包含在标题中...
也许以某种方式将它们命名为__XY左右...


顺便说一句:在java中,私有并不反对覆盖,那只是另一个“副作用”。关键字final反对覆盖

于 2012-12-22T18:19:42.520 回答
1

一个相当可靠的解决方案是使用静态 c 方法进行初始化。毕竟所有有效的c都是有效的objective-c。

例如,CustomSuperclass.h:

#import <Foundation/Foundation.h>
@interface CustomSuperclass : NSObject
@property (nonatomic) NSUInteger integer;
@end

自定义超类.m:

#import "CustomSuperclass.h"
@implementation CustomSuperclass
@synthesize integer = _integer;
static void initializeInstance(CustomSuperclass *self) {
    self.integer = 9;
}
-(id)init{
    if ((self = [super init])){
        initializeInstance(self);
    }
    return self;
}
@end

自定义子类.h:

#import "CustomSuperclass.h"
@interface CustomSubclass : CustomSuperclass
@end

自定义子类.m:

#import "CustomSubclass.h"
@implementation CustomSubclass
// Obviously we would normally use (CustomSubclass *self), but we're proving a point with identical method signatures.
void initializeInstance(CustomSuperclass *self) {
    self.integer = 10;
}
-(id)init{
    if ((self = [super init])){
        // We won't call this classes `initializeInstance()` function.
        // We want to see if this one overrides it's superclasses version.
    }
    return self;
}
@end

现在如果你运行这样的代码:

CustomSuperclass *instanceOfSuperclass = [[CustomSuperclass alloc] init];
NSLog(@"superclass integer:%i", instanceOfSuperclass.integer);

CustomSubclass *instanceOfSubclass = [[CustomSubclass alloc] init];
NSLog(@"subclass integer:%i", instanceOfSubclass.integer);

你会看见:

superclass integer:9
subclass integer:9

如果子类的版本initializeInstance()是子类使用的版本(调用时[super init]),那么子类整数将为 10。这证明了initializeInstance()超类的方法没有被子类的版本覆盖(至少在超类的范围内) .

编辑以回应评论:

我发布了这个答案及其解决方案,不是因为我认为这是解决问题的最佳方法,而是因为它符合问题的规范。问题本质上是,“我如何强制动态语言以静态方式运行,这样我就不必遵循正常的命名/初始化约定?”

处理这种情况的最佳方法是让您的类有一个指定的初始化程序,每个初始化程序都会调用该初始化程序。喜欢:

/* Designated initializer */
-(id)initWithFile:(NSString *)file andURL:(NSURL *)url{
    if ((self = [super init])){
        _url = url;
        _file = file;
        // Initialization of new instance code here.
    }
    return self;
}
-(id)initWithFile:(NSString *)file{
    return [self initWithFile:file andURL:nil];
}
-(id)initWithURL:(NSURL *)url{
    return [self initWithFile:nil andURL:url];
}

指定的初始化器几乎适用于所有情况。它们对于UIView子类来说有点麻烦,因为 IBUIViewinitWithCoder:在实例化代码UIView将/应该调用的地方initWithFrame:调用。在这种情况下,可能会编写一个通用方法以在调用后完成[super initWith...]以减少代码重复。

覆盖通用初始化方法的名称可能会遇到问题。对于大多数人来说,这是通过默默无闻获得一点安全性的地方,例如:

-(void)initialize_CustomUIView{
    // Common init tasks
}
-(id)initWithFrame:(CGRect)frame{
    if ((self = [super initWithFrame:frame])){
        [self initialize_CustomUIView];
    }
    return self;
}
-(id)initWithCoder:(NSCoder *)aDecoder{
    if ((self = [super initWithCoder:aDecoder])){
        [self initialize_CustomUIView];
    }
    return self;
}
-(id)init{
    if ((self = [super init])){
        [self initialize_CustomUIView];
    }
    return self;
}

但是再次,您特别询问了如何禁止自己覆盖通用初始化方法,因此是static void initializeInstance()解决方案。

于 2012-12-22T20:15:28.083 回答
0

在@implementation 中定义或在无名类别中声明的方法不可见,但可以执行。但是,真正的问题是您希望每个实例都有一个初始化方法,但是:

  • 您希望这个方法是私有的,但让这个类的用户知道它存在,并且他们应该覆盖它。如何?未来你不会喜欢这样的。
  • 您想要覆盖此方法并从另一个覆盖的方法中调用它。这导致子方法的双重执行。

您尝试创建的约定没有意义。您应该给每个初始化方法一个不同的名称,并从 init 中调用每个方法。

于 2012-12-22T19:28:44.643 回答
0

这必须在 .m 文件中:

@interface MyClass()

- (void) privateMethod1;

@end

@implementation MyClass

@end
于 2012-12-22T18:30:38.470 回答
0

看看这个页面它是关于最佳客观 c 代码实践的,包括私有方法:)

http://ironwolf.dangerousgames.com/blog/archives/913

于 2012-12-22T18:24:47.233 回答