【问题标题】:Expose a private Objective-C method or property to subclasses向子类公开一个私有的 Objective-C 方法或属性
【发布时间】:2013-12-09 12:49:38
【问题描述】:

根据一些官方的说法,Objective-C 中的类应该只在其头中公开公共方法和属性:

@interface MyClass : NSObject

@property (nonatomic, strong) MyPublicObject *publicObject;

- (void)publicMethod;

@end

私有方法/属性应保存在 .m 文件的类扩展中:

@interface MyClass()

@property (nonatomic, strong) MyPrivateObject *privateObject;

- (void) privateMethod;

@end

而且我认为没有 protected 类型用于私有但可从子类访问的事物。我想知道,除了公开声明私有属性/方法之外,还有其他方法可以实现吗?

【问题讨论】:

  • this 问题类似(实际上相同),简短的回答是不,你不能。

标签: objective-c inheritance subclass visibility declared-property


【解决方案1】:

解决此问题的一种方法是在子类的类扩展中重新声明该属性,然后添加@dynamic 语句,以便编译器不会创建该属性的覆盖实现。所以像:

@interface SuperClass ()

@property (nonatomic, strong) id someProperty;

@end

....


@interface SubClass ()

@property (nonatomic, strong) id someProperty;

@end

@implementation SubClass

@dynamic someProperty;

@end

这显然不理想,因为它复制了一个私有可见的声明。但是在某些情况下它非常方便和有用,所以我会根据具体情况评估这种重复与在公共界面中公开属性所涉及的危险。

Apple 在 UIGestureRecognizer 中使用的另一种方法是在单独的类别头文件中声明属性,明确命名为“私有”或“受保护”,例如“SomeClass+Protected.h”。这样,其他程序员就会知道他们不应该导入该文件。但是,如果您不控制要从其中继承的代码,那就不行了。

【讨论】:

  • 我不敢相信……你是认真的吗?所以你是说我必须在每个子类中复制这些私有变量的声明???太不方便了。。一定有别的办法
  • 您不仅要再次声明它..您还在@dynamic业务中第三次编写它..这是写一个变量3次而不是1次!
  • @abbood 好吧,如果它们真的是私有的,那么子类无论如何都不应该使用它们;)但是 - 我认为拥有 SuperClass+Protected 标头的替代方案是可行的 - 这是 Apple 官方的支持(至少通过示例)。
  • 哦.. 对不起,我混淆了privateprotected.. 所以我只想在我的超类.m 文件中声明protected 变量,并让子类继承它而不重新声明它和这个充满活力的业务.. 这可能吗?
  • posted a full example 了解如何像 Apple 在 UIGestureRecognizerSubclass.h 中一样实现东西
【解决方案2】:

这可以通过使用包含在基类和子类的实现文件中的类扩展(不是类别)来实现。

类扩展的定义类似于类别,但没有类别名称:

@interface MyClass ()

在类扩展中,您可以声明属性,这些属性将能够合成支持的 ivars(XCode > 4.4 自动合成 ivars 也适用于此)。

在扩展类中,您可以覆盖/优化属性(将只读更改为读写等),并添加对实现文件“可见”的属性和方法(但请注意,属性和方法并不是真正的私有的,仍然可以被选择器调用)。

其他人建议为此使用单独的头文件 MyClass_protected.h,但这也可以使用 #ifdef 在主头文件中完成,如下所示:

例子:

BaseClass.h

@interface BaseClass : NSObject

// foo is readonly for consumers of the class
@property (nonatomic, readonly) NSString *foo;

@end


#ifdef BaseClass_protected

// this is the class extension, where you define 
// the "protected" properties and methods of the class

@interface BaseClass ()

// foo is now readwrite
@property (nonatomic, readwrite) NSString *foo;

// bar is visible to implementation of subclasses
@property (nonatomic, readwrite) int bar;

-(void)baz;

@end

#endif

BaseClass.m

// this will import BaseClass.h
// with BaseClass_protected defined,
// so it will also get the protected class extension

#define BaseClass_protected
#import "BaseClass.h"

@implementation BaseClass

-(void)baz {
    self.foo = @"test";
    self.bar = 123;
}

@end

ChildClass.h

// this will import BaseClass.h without the class extension

#import "BaseClass.h"

@interface ChildClass : BaseClass

-(void)test;

@end

ChildClass.m

// this will implicitly import BaseClass.h from ChildClass.h,
// with BaseClass_protected defined,
// so it will also get the protected class extension

#define BaseClass_protected 
#import "ChildClass.h"

@implementation ChildClass

-(void)test {
    self.foo = @"test";
    self.bar = 123;

    [self baz];
}

@end

当您调用#import 时,它基本上会将 .h 文件复制粘贴到您要导入它的位置。 如果您有#ifdef,则只有在设置了具有该名称的#define 时,它才会包含其中的代码。

在您的 .h 文件中,您没有设置定义,因此任何导入此 .h 的类都不会看到受保护的类扩展。 在基类和子类 .m 文件中,在使用 #import 之前使用 #define,以便编译器包含受保护的类扩展。

【讨论】:

    【解决方案3】:

    虽然其他答案都是正确的,但我想补充一下......

    私有、受保护和公共可用,例如变量

    @interface MyClass : NSObject {
    @private
      int varA;
    
    @protected
      int varB;
    
    @public
      int varC;
    }
    
    @end
    

    【讨论】:

    • 这些是实例变量,objc没有类变量的概念。
    • 很高兴知道这一点。如果您需要与子类共享实例变量,@protected 就是这样做的方法。
    • 这很好,但@properties 不是实例变量。最多 - 他们有这样的 iVar 的后备存储,即使那样,您也无法比控制方法的可见性更好地控制这些 iVar 的可见性。
    【解决方案4】:

    您唯一的选择是在头文件中将其声明为公共。如果您想至少保留一些方法分离,您可以创建一个类别并将所有受保护的方法和属性放在其中,但最终所有内容仍将是公开的。

    #import "MyClass.h"
    
    @interface MyClass (Protected)
    
    - (void) protectedMethods;
    
    @end
    

    【讨论】:

    • 不需要类别。通过跳过 … (Protected) 使其成为类扩展。
    • @Regexident 这里的重点是高子分类器,protectedMethod 在那里用于覆盖。命名扩展(受保护)是一个完美的解决方案。
    【解决方案5】:

    只需使用您的类扩展名创建一个 .h 文件。将此导入您的 .m 文件。顺便说一句,这是在不破坏封装的情况下测试私有成员的好方法(我并不是说您应该测试私有方法:))。

    // MyClassProtectedMembers.h
    @interface MyClass()
    
    @property (nonatomic, strong) MyPrivateObject *privateObject;
    - (void) privateMethod;
    @end
    

    ////////////////

    #import "MyClassProtectedMembers.h"
    
    @implementation MyClass
    // implement privateMethod here and any setters or getters with computed values
    @end
    

    这里是这个想法的要点:https://gist.github.com/philosopherdog/6461536b99ef73a5c32a

    【讨论】:

      【解决方案6】:

      我看到了使属性可见的好答案,但我没有看到在任何这些答案中都非常清楚地公开了这些方法。以下是我如何使用 Category 成功地将私有方法公开给子类:

      SomeSuperClass.m:

      @implementation SomeSuperClass
      
      -(void)somePrivateMethod:(NSString*)someArgument {
          ...
      }
      

      SomeChildClass.h

      @interface SomeChildClass : SomeSuperClass
      

      SomeChildClass.m

      @interface SomeSuperClass (exposePrivateMethod)
      -(void)somePrivateMethod:(NSString*)someArgument;
      @end
      
      @implementation SomeChildClass
      
      -(void)doSomething {
          [super somePrivateMethod:@"argument"];
      }
      
      @end
      

      【讨论】:

        【解决方案7】:

        那是因为私人和公共之间甚至没有真正的区别。虽然编译器可能会警告您缺少某个方法或实例变量的接口,但您的程序仍然可以工作。

        【讨论】:

          猜你喜欢
          • 2013-10-06
          • 1970-01-01
          • 2015-08-19
          • 2013-10-10
          • 2012-10-29
          • 2014-01-14
          • 2011-08-04
          • 2013-11-27
          相关资源
          最近更新 更多