35

根据一些官方的说法,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类型可以用于私有但可从子类访问的事物。我想知道,除了公开声明私有属性/方法之外,还有什么方法可以实现这一点?

4

7 回答 7

35

解决此问题的一种方法是在子类的类扩展中重新声明该属性,然后添加一条@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”。这样,其他程序员就会知道他们不应该导入该文件。但是,如果你不控制你继承的代码,那不是一个选择。

于 2012-09-28T04:46:07.480 回答
16

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

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

@interface MyClass ()

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

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

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

例子:

基类.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

基类.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

子类.h

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

#import "BaseClass.h"

@interface ChildClass : BaseClass

-(void)test;

@end

子类.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 文件中,在使用#define之前使用#import,以便编译器包含受保护的类扩展。

于 2014-02-06T15:53:20.630 回答
8

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

Private、protected 和 public用于实例变量,如下所示:

@interface MyClass : NSObject {
@private
  int varA;

@protected
  int varB;

@public
  int varC;
}

@end
于 2012-09-28T04:49:16.937 回答
1

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

#import "MyClass.h"

@interface MyClass (Protected)

- (void) protectedMethods;

@end
于 2012-09-28T04:36:09.557 回答
1

只需使用您的类扩展名创建一个 .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

于 2015-04-29T19:52:24.550 回答
1

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

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
于 2016-09-02T00:02:44.717 回答
0

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

于 2012-09-28T04:38:17.777 回答