【问题标题】:How to make an objective-c method private [duplicate]如何将objective-c方法设为私有[重复]
【发布时间】:2012-12-22 18:17:50
【问题描述】:

可能重复:
Why doesn’t Objective-C support private methods?
Private Method Declaration 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
{
    ...
}

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

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

- (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 接口文件,因此完全有理由认为它可以实现一个初始化方法,而不会导致改变行为的不良副作用基类。

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

【问题讨论】:

标签: objective-c


【解决方案1】:

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

例如,CustomSuperclass.h:

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

CustomSuperclass.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

CustomSubclass.h:

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

CustomSubclass.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 子类来说有点麻烦,因为 IB UIView 将调用 initWithCoder: ,而代码实例化 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() 解决方案。

【讨论】:

  • 是的...我认为 C 关键字“static”对此很有用,因为它用于隐藏当前文件范围之外的可见性。我不确定这是最好的答案,但它确实解决了“我如何防止这种情况发生?”的问题。所以谢谢!
  • 我同意静态方法可能不是最好的解决方案。我用详细信息编辑了我的答案。
【解决方案2】:

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

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


btw:在 java 中,private 并不反对覆盖,那只是另一个“副作用”。关键字 final 反对覆盖

【讨论】:

  • 我知道这是一个副作用,但它在防止子类改变行为方面也很有效。我也知道我可以使用“ClassName_initialize”作为方法名称或类似名称的标准。此外,Java 中的“final”意味着防止覆盖。我只想要一个恰好实现同名方法的子类,以免意外破坏基类中的某些内容。
  • ...嗯是的..反正没有可比性
【解决方案3】:

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

Apple 经常这样做。

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

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

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

【讨论】:

  • 名字中的一个 _ 是为苹果 IIRC 保留的,所以这不是一个好建议 IMO
  • @Daij-Djan 我在一堆 3rd 方代码中看到过这个,但我想你是对的,所以他可以使用 - (void)myPrefix_someMethod;
  • @Daij-Djan 这是一个神话。谁告诉你你不能在方法名中使用下划线(我以前见过方法有三个下划线)。编译器真的不废话。 +1 平衡。
  • 当然你可以在技术上使用它们,但是你会遇到覆盖私有 API 的危险。
    顺便说一句,名称在运行时保留。这当然根本不是编译器问题,它只是惯例之一!
  • @Daij-Djan 我猜你在技术上是正确的,使用你自己的前缀更干净。然而,覆盖私有方法的可能性非常小。此外,苹果还有很多不带前缀的私有方法。
【解决方案4】:

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

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

【讨论】:

    【解决方案5】:

    这必须在 .m 文件中:

    @interface MyClass()
    
    - (void) privateMethod1;
    
    @end
    
    @implementation MyClass
    
    @end
    

    【讨论】:

    • 不。一个子类可以实现它自己的 privateMethod1,它会被调用。我尝试了类扩展,但它们没有帮助。
    • 这很明显:在 Objective-C 中,消息传递是动态的。
    • @lar3ry 你说得对,你甚至可以覆盖苹果的“私有”方法。
    • @lar3ry 正如你现在被告知 n 次:objC 中既没有 private 也没有 final
    • 确实有@private,但对于 ivars。我知道你知道,只是对 la3ry 的一点精确。
    【解决方案6】:

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

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

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

    【讨论】:

      猜你喜欢
      • 2011-01-23
      • 2010-09-16
      • 1970-01-01
      • 1970-01-01
      • 2012-05-06
      • 1970-01-01
      • 2010-10-13
      • 2022-06-11
      • 1970-01-01
      相关资源
      最近更新 更多