3

我所有的研究都表明@private指令没有真正的用途——所以我一定是遗漏了一些东西,需要各位专家加入:-)

假设我们有 2 个类:一个Car类和一个SportsCar类,其中SportsCarCar的子类。

这是汽车类:

@interface Car : NSObject {
    NSString *make;
    NSString *model;

    @private
    int numberOfBackSeatPassengers;  // I'm making this a private iVar cause I'm just gonna
                // say that all Sportscars will be 2-seaters and therefore shouldn't
                // be able to set/get the number of back-seat passengers
}

@property (nonatomic, strong) NSString *make, *model;

// Now here's my first issue: if I also make "numberOfBackSeatPassengers" an @property 
// then it seems like all subclasses of this Car class *WILL* be able to access it as 
// well - even though I declared it as @private - but I'll do this anyway to make my point:

@property int numberOfBackSeatPassengers;

@end

实现如下所示:

@implementation Car

@synthesize make, model, numberOfBackSeatPassengers;

@end

现在这里是跑车类:

#import "Car.h"

@interface Sportscar : Car

@property int turboEngineSize;

@end

及其实现:

#import "Sportscar.h"

@implementation Sportscar

@synthesize turboEngineSize;

@end

在“主要”我有这个:

    Car *car1 = [[Car alloc] init];
    [car1 setMake:@"Chevy"];
    [car1 setModel:@"Impala"];
    [car1 setNumberOfBackSeatPassengers:3];

    Sportscar *sports1 = [[Sportscar alloc] init];
    [sports1 setMake:@"Audi"];
    [sports1 setModel:@"tt"];
    [sports1 setNumberOfBackSeatPassengers:3];

显然,我可以在 Sportscar 上设置NumberOfBackSeatPassengers - 即使 iVar 被声明为@private - 但那是因为我在“ Car.h ”中将其设为 @property,这意味着它的合成 getter 和 setter 是 Instance方法,因此可用于Car的所有子类。

另一种选择是不在“Car.h”中将numberOfBackSeatPassengers声明为@property,仅将其保留为简单的 iVar,而是在“Car.m”的@implementation 中手动为其创建 Setter 和 Getter像这样:

-(void) setNumberOfBackSeatPassengers:(int)numPassgeners {
    numberOfBackSeatPassengers = numPassgeners;
}

-(int)numberOfBackSeatPassengers {
    return numberOfBackSeatPassengers;
}

这将使numberOfBackSeatPassengers的 getter 和 setter仅在“Car.m”中可用——我想这会使它们“私有”——但它们私密了:我永远无法从 Main 或任何外部的任何地方调用它们“Car.m” 此外,这是真正的重点:这样做意味着“Car.h”中的 @private 指令在任何这些中都没有真正发挥作用。我的意思是我现在可以回到“Car.h”,在那里取出“ @private”指令——我的numberOfBackSeatPassengers的手动设置器和获取器仍然可以像现在一样工作,据说是私有的——那是什么通过“@private”获得 ? 它是如何真正发挥作用的?

任何人都可以对此有所了解吗?

(是的,我知道我可以在“Car.m”文件的@interface 部分扩展我的 Car 类 - 通过一个类别,或者先将numberOfBackSeatPassengers设为只读属性,然后将其更改为 readwrite 等 - 但这些都是似乎是使“@private”工作的变通方法或“黑客”。我只是不明白@private 如何真正独立工作。

==================================================== ===

编辑 - 回应以下aroth的评论:

1) aroth 说子类在理论上仍然可以调用未在其父类的 Header 中声明的方法是绝对正确的——通过使用performSelector. 我说“理论上”,因为在我的情况下它不能正常工作如果 - 在“主要”中 - 我打电话

[sportscar1 performSelector:@selector(setNumberOfBackSeatPassengers:)];

然后我插入了一些垃圾号码,numberOfBackSeatPassengers因为在以这种方式调用该方法时,我无法明确地将数字作为参数传入。

(问题:有没有办法解决这个问题?)

2) aroth 的说法也绝对正确,Sportscar我们可以简单地覆盖Car类的 setter 和 getter numberOfBackSeatPassengers,并让这些覆盖方法将其重置为 0,或者给出错误等。但是虽然这是一个非常实用的解决方案,而且似乎可以解决这个特殊的问题,我觉得它并没有解决更大的问题,即@private似乎没有真正做它应该做的事情。

3)重新设计逻辑以拥有一个类FourDoorCar和另一个 类TwoDoorCar,然后继续构建它是一个有趣的选择 - 但这几乎感觉就像现在 Objective-C 的语法正在“强制”我的编程逻辑以及我的方式构建我的项目——这感觉像是一种强加。也许我错了,并且对此做了太多 - 但无论哪种方式,这一切都只是因为@private没有做它似乎承诺的事情......?感觉不对。

在一天结束时,我不断地回到同一个问题:@private实际上对我们有什么好处?它有什么好处,它给我“买”了什么?似乎如果我想让 iVar 是私有的,我可以在“.m”文件中声明它,而不必首先在头文件中声明它。我的意思是我是否正确?或者是否仍然存在一些实例,您希望在 Header 中声明 iVar 为@private,但不在Header 中为它声明 setter 和 getter - 所以这些不会显式地可用于子类 - 并让它全部感觉?

我们能想出一个实际的例子吗?是否有某种我想@private 在标题中声明的 Car 属性(而不是在“.m”中)会以某种方式使我受益?我认为numberOfBackSeatPassengers这将是一个很好的例子,但我没有看到它在实际代码中如何真正起作用......

==================================================== ========================

编辑#2 - 继续与@aroth 对话:-)

@aroth - 我绝对同意它更好/更有条理地在标头中声明所有 iVar,而不是将内容拆分,以便有些在标头中,有些在实施中。这造成了混乱,我真的不喜欢这种方法。(我在最初的问题中指出,我不想使用实施和/或类别方法来解决我的问题。) -另外,是的,属性绝对不必总是由 iVar 支持。

-关于适当地设计类,我同意这当然是良好编程的关键。Car/Sportscar 的例子是我当场编造出来的,目的是为我的问题提供一些背景信息,考虑到它的设计优点/缺陷,我没有投入任何时间。我认为,如果我们采用您的方法 - 这似乎很合乎逻辑 - 并使用 Car 类、FourDoorCar 子类、TwoDoorCar 子类等 - 我们可以解决很多问题 - 但它仍然很可能迟早我们会遇到这样一种情况,我们可能想要一个 @private iVar 用于我们的一个类,并且不想创建另一个子类来处理它。我的意思是让我们假设这会发生,为了讨论。

因此,如果可能的话,我真的很想为我们的 Car 类考虑一个特定的 iVar,将其作为@private是有意义的,在代码中展示如何使用它,并讨论它的范围和限制。

我一直在想一个真实世界的例子,说明我们只希望 Car 拥有的 Car 的某些属性——并且它的任何子类都不应该继承。我真的认为numBackSeatPassengers可以解决问题 - 就我们讨论的目的而言,它仍然可以,但是,我会再编一个并称它为phantomIVar:-)

所以:

@interface Car : NSObject {

    @private
    //int numberOfBackSeatPassengers;  
    int phantomIVar;

}

@property (nonatomic, strong) NSString *make, *model;


@end

实施将是:

@implementation Car

@synthesize make, model;

-(void) setPhantomIVar:(int)i {
    phantomIVar = i;
}

-(int)phantomIVar {
    return phantomIVar;
}

@end

这几乎让我们回到了我们开始的地方:-)

至少我是这样的感觉。

@private我的意思是声明似乎给我们带来的唯一好处是可读性。所以现在,任何查看 Header 的人都可以看到这phantomIVar是 Car 的 iVar,并了解它是私有的。就是这样。

然而,就功能而言,它似乎并没有做太多。因为它不像把我们放在@private前面phantomIVar释放了我们仍然能够在 Header 中为它编写一个 setter/getter 并且只有 Car 类对象而不是 Car 的子类可以访问它们。不,@private不明白。要获得隐私,您必须进入实施文件并在那里编写您的 setter 和 getter。最终在 Objective-C 中没有私有方法之类的东西。在对象。C. 他们都是公开的。

aroth,如果我做对了,请告诉我 - 如果没有,我到底哪里出错了。

非常感谢 :-)

4

2 回答 2

0

您可以将属性声明为readonlyCar类本身中,或者将其重新声明为readonly仅在SportsCar类中。

此外,@private与属性没有任何关系 - 它只修改 ivar 本身的范围。

于 2012-09-15T12:31:07.120 回答
0

这将使 numberOfBackSeatPassengers 的 getter 和 setter 仅在“Car.m”中可用

不对。这些方法仍然存在于Car扩展的每个对象的每个实例和每个实例上Car,无论您是否在头文件中声明它们。如果您尝试直接调用它们,编译器不会将它们视为公开可见的,并且会抱怨,但您仍然可以通过Car简单地在任何子类上调用 getter 和 setter performSelector:

在任何情况下,如果你有一个在支持它的 ivar@property上使用是没有意义的@private(并且有一个明确的 ivar 支持它也是没有意义的,当你使用时会自动为你创建一个@synthesize;但这是一个单独的主题) . 我建议如果SportsCar旨在扩展Car并且永远不允许记录任何后座乘客,那么“标准”方法就是简单地覆盖 getter/setter 方法SportsCar以始终设置/返回 0 或提高一些如果尝试设置非零值,则会出错。

由于此属性不适用于所有Car实例,另一种选择是将其完全从基类中取出。例如,您可以拥有Car,然后从该拥有TwoDoorCarFourDoorCar,然后SportsCar从 衍生TwoDoorCar。在这种情况下,您可以将其申报numberOfBackSeatPassengers为公共财产FourDoorCar,因为每辆四门车都应该能够容纳后座的乘客。

回到最初提出的问题,在 ivar 上使用@private只会影响该 ivar 的可见性。它不影响使用 ivar 的方法。所以一个子类Car将无法看到numberOfBackSeatPassengersivar 本身。但是由于您已经为它创建了一个公共 getter/setter,子类当然可以看到这些,并使用它们来修改 ivar 的值。

编辑

简要回答更新的问题:

  1. 是的,您可以使用NSInvocation动态调用需要原始参数的方法。或者您可以使用这里讨论的方法,这种方法更直接: Objective-C 和 SEL/IMP 的使用。或者您可以使用 aNSNumber而不是 anint然后使用performSelector:withObject:.

  2. 我不确定你在说什么@private应该在这种情况下做。您认为 using@private应该做什么?

  3. 我认为这与语法关系不大,而与面向对象设计的原则有关。如果有些汽车没有后座,那么给Car超类一个numberOfBackseatPassengers属性就不是很好的面向对象设计。这样做会给对象一个字段,该字段实际上并不适用于对象类型的每个实例。当您开始这样做时,您会遇到您在示例中描述的那种问题。超类的目的是包含其所有派生类型共有的功能。如果它具有仅对其某些派生类型通用的功能,那么这通常是一个设计问题。无论如何,它与 Objective-C 语法或语义无关。

至于你@private得到了什么,简化你的班级组织怎么样,一方面?是的,你可以在你的实现文件中声明一个 ivar 来实现类似的效果,但这真的和在头文件中声明所有 ivar 一样方便吗?在一个相当复杂的项目中,如果仅在标头中声明了一些ivars 而其余的在实现文件中 ,其他开发人员是否能够轻松地遵循您的代码?

如果没有@private/在标头中声明的@protected 每个ivar 都将是公共的,这在面向对象的环境中绝对不好,因为 Jonathan 指出的所有原因。因此,这些访问修饰符的存在可能首先是为了解决这个问题。

至于用例,带有 getter/setter 的属性可能不是最好的例子。getter/setter 的目的实际上总是提供一个公共接口来修改/查询属性值,并且正如 Objective-C 中所指出的,没有必要在任何范围内显式声明 ivar 来支持合成属性。

一个更好的例子可能是IBOutlet's. 您希望在您的标头中声明这些,以便 XCode/Interface Builder 可以找到它们,但您不希望它们暴露在您的类实现之外,或者(通常)甚至暴露给您的类的子类。因此,您将在您的标头中声明它们,并且您通常不会为这些 ivars 添加任何 getter/setter 方法。

编辑 2

对于哪里@private有意义的具体示例,例如:

@interface Car : NSObject {

    @private
    DataRecorder* blackBoxRecorder;

}

@property (nonatomic, strong) NSString *make, *model;

@end

我们知道,拟议的法规可能要求道路上的所有汽车都配备内置的黑匣子/数据记录器。所以每个人都Car必须有一个,并且任何子类Car都不能篡改blackBoxRecorder

在这种情况下,定义一个 setter 方法是没有意义的。您可以提供一个公共 getter,或者您可以提供一个公共包装 API,围绕DataRecorder子类可以用来记录数据。类似的东西-(void) logEventWithName:(NSString*)name andValue:(NSNumber*)value;。因此子类可以DataRecorder通过 API 使用,但它们不能与支持 ivar 本身混淆以禁用或修改强制黑盒/数据记录器的行为。

但无论如何,是的,我基本同意你的分析。@private主要影响代码的可读性/可维护性。Objective-C 作为面向对象的编程语言需要存在才能成功(如果默认情况下所有 ivars 都是公共的,并且无法修改它,那么该语言将一团糟),但是它从一个纯粹的功能角度并不多。它更像是一种逻辑/组织工具。它有助于数据隐藏,并允许您将所有 ivars 保留在头文件中,仅此而已。

于 2012-09-15T12:39:08.023 回答