将iVars放在“现代” Objective-C中的何处?


81

雷·温德利奇(Ray Wenderlich)的《 iOS6 by Tutorials》一书中有一章很好地讲述了如何编写更多“现代” Objective-C代码。在其中的一小节中,这些书描述了如何将iVars从类的标题移到实现文件中。由于所有iVar都应该是私有的,因此这似乎是正确的选择。

但是到目前为止,我发现了三种方法。每个人的做法都不同。

1.)将iVars放在大括号内的@implementantion下(这是本书中的做法)。

2.)将iVars放在@implementantion下,没有大括号

3.)将iVars放在@implementantion(类扩展名)上方的专用接口内

所有这些解决方案似乎都可以正常工作,到目前为止,我还没有注意到我的应用程序行为上的任何差异。我猜没有做到这一点的“正确”方法,但是我需要编写一些教程,我只想为我的代码选择一种方法。

我应该走哪条路?

编辑:我只在这里谈论iVars。不是属性。对象仅需要自身的其他变量,并且不应暴露在外部。

代码样例

1)

#import "Person.h"

@implementation Person
{
    int age;
    NSString *name;
}

- (id)init
{
    self = [super init];
    if (self)
    {
        age = 40;
        name = @"Holli";
    }
    return self;
}
@end

2)

#import "Person.h"

@implementation Person

int age;
NSString *name;


- (id)init
{
    self = [super init];
    if (self)
    {
        age = 40;
        name = @"Holli";
    }
    return self;
}
@end

3)

#import "Person.h"

@interface Person()
{
    int age;
    NSString *name;
}
@end

@implementation Person

- (id)init
{
    self = [super init];
    if (self)
    {
        age = 40;
        name = @"Holli";
    }
    return self;
}
@end

4
对于“正确”没有评论,但另一个选项(我似乎正在朝着这个方向迈进)是完全转储iVars,并将所有内容都设置为属性(通常在.m文件的类扩展名之内)。一致性使我不必考虑状态的实现细节。
菲利普·米尔斯

1
如果您将问题更新为显示三个示例中的每个示例的真实代码示例,则该问题将对其他人真正有益。我个人只使用选项1,所看到的选项3,但我不熟悉的选项2
rmaddy

1
谢谢妈妈。我添加了一些示例代码。
TalkingCode

4
抱歉,刚注意到更新。选项2无效。这不会产生错误。这将创建文件全局变量。该类的每个实例将共享该一组变量。您可以给他们分类变量,而不是实例变量。
rmaddy2012年

Answers:


162

将实例变量放入@implementation块或类扩展中的能力是“现代Objective-C运行时”的一项功能,每个版本的iOS和64位Mac OS X程序都使用该功能。

如果要编写32位Mac OS X应用程序,则必须将实例变量放在@interface声明中。不过,您可能不需要支持应用程序的32位版本。自五年前发布的10.5版(Leopard)以来,OS X支持64位应用程序。

因此,假设您仅在编写将使用现代运行时的应用程序。您应该将ivars放在哪里?

选项0:在@interface(请勿执行)中

首先,让我们去了,为什么我们希望把实例变量在@interface声明。

  1. 将实例变量放入可以@interface向类用户公开实现的详细信息。这可能导致这些用户(甚至在使用自己的类时甚至是您自己!)也要依赖他们不应该使用的实现细节。(这与我们是否声明ivars无关)@private

  2. 将实例变量放入make中@interface会使编译花费更长的时间,因为每次添加,更改或删除ivar声明时,我们都必须重新编译每个.m导入接口的文件。

因此,我们不想将实例变量放在中@interface。我们应该放在哪里?

选项2:在@implementation没有括号的情况下(不要这样做)

接下来,让我们讨论您的选项2,“将iVars放在@implementantion下而没有大括号的情况”。这并没有声明实例变量!您正在谈论此:

@implementation Person

int age;
NSString *name;

...

该代码定义了两个全局变量。它不声明任何实例变量。

如果需要全局变量,则可以在.m文件中甚至在中定义全局变量,@implementation例如,因为您希望所有实例共享某种状态(例如缓存),因此很好。但是您不能使用此选项声明ivars,因为它没有声明ivars。(此外,通常应声明实现专用的全局变量,static以避免污染全局名称空间和冒链接时错误的风险。)

剩下的选项1和3。

选项1:在@implementation带花括号的情况下(Do It)

通常,我们要使用选项1:将它们@implementation放在大括号中,如下所示:

@implementation Person {
    int age;
    NSString *name;
}

我们将它们放在这里是因为它使它们的存在保持私有状态,从而防止了我前面描述的问题,并且通常没有理由将它们放入类扩展中。

那么,什么时候我们要使用您的选项3,将它们放在类扩展中?

选项3:在课程扩展中(仅在必要时执行)

几乎没有理由将它们放在与类的文件相同的文件中的类扩展中@implementation@implementation在这种情况下,我们最好将它们放入。

但是有时我们可能会编写一个足够大的类,以便将其源代码分成多个文件。我们可以使用类别来做到这一点。例如,如果要实现UICollectionView(一个相当大的类),我们可能会决定要将管理可重用视图(单元格和补充视图)队列的代码放在单独的源文件中。我们可以通过将这些消息分成一个类别来做到这一点:

// UICollectionView.h

@interface UICollectionView : UIScrollView

- (id)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout;
@property (nonatomic, retain) UICollectionView *collectionViewLayout;
// etc.

@end

@interface UICollectionView (ReusableViews)

- (void)registerClass:(Class)cellClass forCellWithReuseIdentifier:(NSString *)identifier;
- (void)registerNib:(UINib *)nib forCellWithReuseIdentifier:(NSString *)identifier;

- (void)registerClass:(Class)viewClass forSupplementaryViewOfKind:(NSString *)elementKind withReuseIdentifier:(NSString *)identifier;
- (void)registerNib:(UINib *)nib forSupplementaryViewOfKind:(NSString *)kind withReuseIdentifier:(NSString *)identifier;

- (id)dequeueReusableCellWithReuseIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath*)indexPath;
- (id)dequeueReusableSupplementaryViewOfKind:(NSString*)elementKind withReuseIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath*)indexPath;

@end

好的,现在我们可以在中实现主要UICollectionView方法,UICollectionView.m并且可以在中实现管理可重用视图的方法UICollectionView+ReusableViews.m,这使我们的源代码更易于管理。

但是我们可重用的视图管理代码需要一些实例变量。这些变量都被暴露在主类@implementationUICollectionView.m,所以编译器将在发出这些.o文件。并且我们还需要将这些实例变量暴露给中的代码UICollectionView+ReusableViews.m,以便这些方法可以使用ivars。

这是我们需要类扩展的地方。我们可以将reusable-view-management ivars放在私有头文件的类扩展中:

// UICollectionView_ReusableViewsSupport.h

@interface UICollectionView () {
    NSMutableDictionary *registeredCellSources;
    NSMutableDictionary *spareCellsByIdentifier;

    NSMutableDictionary *registeredSupplementaryViewSources;
    NSMutableDictionary *spareSupplementaryViewsByIdentifier;
}

- (void)initReusableViewSupport;

@end

我们不会将此头文件发送给我们库的用户。我们只是将其导入inUICollectionView.m和in UICollectionView+ReusableViews.m,以便需要查看这些ivars的所有内容都可以看到它们。我们还抛出了一个方法,我们希望maininit方法可以调用该方法来初始化reusable-view-management代码。我们将调用该方法从-[UICollectionView initWithFrame:collectionViewLayout:]UICollectionView.m,我们会实现它UICollectionView+ReusableViews.m


4
选项3:在以下情况下也可以使用:1)您想要对自己的ivars强制使用属性,甚至在您自己的实现中(@property int age),以及2)您要覆盖只读公共属性(@property(readonly)int age)在标头中允许您的代码在实现中以读写方式访问属性(@property(readwrite)int年龄)。除非有其他方法,否则@rob mayoff?
leanne

@leanne如果要覆盖readonly属性以私有地成为readwrite,则需要使用类扩展名。但是问题是关于在哪里放置Ivars,而不是在哪里放置财产声明。我看不到使用类扩展如何阻止实现访问ivars。为此,您需要将方法实现放在一个类别中,因为编译器必须先查看所有声明ivar的类扩展,然后才能看到@implementation
rob mayoff

@rob mayoff,对,这是对的-您提出了正确的观点。霍利(Holli)指出此处的属性不是问题,就其用途而言,“强制执行”对我而言有点强。无论如何,如果要使用属性来访问其ivar,而不是直接进行访问,那将是这样做的方法。当然,您仍然可以直接使用'_'(_age = someAge)访问ivars。我添加评论是因为我认为讨论ivars时值得一提财产的使用,因为很多人一起使用它们,这就是这样做的方法。
leanne

那就是“选项0:在@interface中(不要这样做)”。
rob mayoff

5

选项2完全错误。这些是全局变量,而不是实例变量。

选项1和3基本相同。绝对没有区别。

选择是将实例变量放在头文件中还是在实现文件中。使用头文件的优点是,您可以使用快捷简便的键盘快捷键(在Xcode中为Command + Control + Up)来查看和编辑实例变量和接口声明。

缺点是您在公共标头中公开了类的私有详细信息。在某些情况下,这是不理想的,尤其是当您正在编写代码供他人使用时。另一个潜在的问题是,如果您使用的是Objective-C ++,最好避免在头文件中放入任何C ++数据类型。

在某些情况下,实现实例变量是不错的选择,但是对于我的大多数代码,我仍然将实例变量放在标头中,只是因为对于使用Xcode的编码人员来说,对我来说更方便。我的建议是做任何您觉得更方便的事情。


4

在很大程度上,它与ivar对子类的可见性有关。子类将无法访问该@implementation块中定义的实例变量。

对于我计划分发的可重用代码(例如,库或框架代码),而我不希望公开实例变量供公众检查,那么我倾向于将ivars放在实现模块中(您的选择1)。


3

您应该将实例变量放在实现上方的私有接口中。选项3。

要阅读的文档是《Objective-C中编程》指南。

从文档中:

您可以定义不带属性的实例变量

最佳做法是在需要跟踪值或另一个对象时随时在对象上使用属性。

如果确实需要在不声明属性的情况下定义自己的实例变量,则可以将其添加到类接口或实现顶部的括号内,如下所示:


1
我同意你的看法:如果要定义ivar,私有类扩展是最好的选择。令人奇怪的是,尽管您所引用的文档不仅没有对应该定义ivars的位置(仅列出了所有三种选择),而且更进一步,建议您根本不应该使用ivars,但是而是“使用属性的最佳做法”。到目前为止,这是最好的建议。
罗布

1

真正的公共ivars应该在@interface中声明为属性(可能是您在1中所想的)。如果您正在运行最新的Xcode并使用现代运行时(64位OS X或iOS),则可以在@implementation(2)中声明私有ivars,而不是在类扩展中声明,这可能是您在重新思考3。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.