正确使用@private变量/属性



我所有的研究都表明,@private指令没有真正的用法-所以我一定错过了什么,需要你们专家帮忙:-)

假设我们有两个类:Car类和SportsCar级,其中跑车Car的子类。

这是汽车类别:

@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];

很明显,我可以在跑车上设置NumberOfBackSeatPassengers-尽管iVar被声明为@private,但这是因为我在"Car.h"中将其设置为>属性——这意味着它的合成getter和setter是实例方法,因此可用于Car的所有子类。

另一种选择是不在"Car.h"中声明numberOfBackSeatPassengers为@属性,将其作为一个简单的iVar保留在那里,而手动在"Car.m"的@实现中为其创建一个Setter和Getter,如下所示:

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

这将使numberOfBackSeatPassenger的getter和setter只能在"Car.m"中使用-我想这将使它们成为"私有"-但它们也将是私有的:我永远不能从Main或"Car.m"之外的任何地方呼叫它们。此外,这才是真正的意义:这样做意味着"Car.h"中的@private指令在这一切中都没有真正发挥作用。我的意思是,我现在可以回到"Car.h",去掉那里的">@private">指令,而我的numberOfBackSeatPassenger手动设置器和getter仍然可以像现在一样工作,被认为是私有的,那么"@privaty"会带来什么呢?它是如何真正发挥作用的?

有人能真正阐明这一点吗?

(是的,我知道我可以在"Car.m"文件的@interface部分扩展我的Car类-通过一个类别,或者先将numberOfBackSeatPassengers设置为只读属性,然后将其更改为readwrite等-但这些似乎都是让"@private"工作的变通方法或"技巧"。我只是不知道@private是如何单独工作的。)

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

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

1) aroth说一个子类理论上仍然可以通过使用performSelector调用未在其父类的Header中声明的方法,这是绝对正确的。我说"理论上",因为在我的情况下,它不太工作正确:如果-在"主要"-我调用

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

然后我为numberOfBackSeatPassengers插入了一些垃圾数字,因为在以这种方式调用方法时,我不能显式地将数字作为参数传递。

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

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

3) 重新设计逻辑,为FourDoorCarTwoDoorCar分别提供一个类,然后继续在此基础上构建,这是一个有趣的选择——但现在感觉Objective-C的语法几乎是在"强迫"我的编程逻辑和我如何构建我的项目——这感觉像是一种强加。也许我错了,从中得到了太多——但不管怎样,这一切的发生都只是因为@private没有做它似乎承诺的事情。。。?感觉不对。

一天下来,我不断回到同一个问题:@private到底对我们有什么好处?它有什么好处,它为我"买"了什么?似乎如果我想让iVar是私有的,我可以在".m"文件中声明它,而不必首先在头文件中声明。我的意思是我说的对不对?或者,是否还有一些实例中,您希望在Header中声明一个iVar为@private,但而不是在Header中将其声明一个setter和getter,这样子类就不能显式地使用它们了,这一切都有意义吗?

我们能想出一个实际的例子吗?是否有某种Car属性,我想在Header中声明为@private(而不是在".m"中),这对我有好处?我认为numberOfBackSeatPassengers将是一个很好的例子,但我不知道它在实际代码中是如何真正工作的。。。

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

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

@aroth-我绝对同意,在Header中声明所有iVar,而不是将事情拆分,这样有些在Header,有些在Implementation中会更好/更有组织性。这造成了混乱,我真的不喜欢这种做法。(我在最初的问题中指出,我不想使用实施和/或类别方法来解决我的问题。)-此外,是的,属性绝对不必总是由iVars备份。

-关于恰当地设计课程,我同意这当然是良好编程的关键。汽车/跑车的例子是我当场编造的,目的是给我的问题一些背景,我没有花任何时间考虑它的设计优点/缺陷。然而,我认为如果我们采用你的方法——这似乎是非常合乎逻辑的——并使用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声明似乎为我们购买的唯一东西是可读性。因此,现在,任何查看标题的人都将能够看到phantomIVar是汽车的iVar,并理解它是私有的。就是这样。

然而,就功能而言,它似乎并没有起到多大作用。因为这不像是把@private放在phantomIVar前面,这样我们就可以在Header中为它写一个setter/getter,并且只能访问Car类对象,而不能访问Car的子类。不,@private不会让你明白。为了获得隐私,您必须进入实现文件,并在那里编写setter和getter。最终在Objective-C中没有私有方法。在Obj.C.中,它们都是公开的。

阿罗斯,请让我知道我是否做对了——如果没有,我到底错在哪里了。

非常感谢:-)

这将使后座乘客数量仅在"Car.m"内可用

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

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

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

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

编辑

简要回答更新的问题:

  1. 是的,可以使用NSInvocation动态调用需要基元参数的方法。或者,您可以使用这里讨论的方法,它甚至更简单:Objective-C和SEL/IMP的使用。或者您可以使用NSNumber而不是int,然后使用performSelector:withObject:

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

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

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

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

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

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

编辑2

对于@private有意义的具体例子,比如

@interface Car : NSObject {
@private
DataRecorder* blackBoxRecorder;
}
@property (nonatomic, strong) NSString *make, *model;
@end

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

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

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

您可以在Car类本身中将属性声明为readonly,也可以仅在SportsCar类中将其重新声明为readonly

此外,@private与属性无关——它只修改ivar本身的范围。

最新更新