83

Ray Wenderlich 的“iOS6 by Tutorials”一书有一个关于编写更“现代”的 Objective-C 代码的非常好的章节。在一个部分中,这些书描述了如何将 iVar 从类的头文件移动到实现文件中。由于所有 iVar 都应该是私有的,这似乎是正确的做法。

但到目前为止,我找到了 3 种方法。每个人都在做不同的事情。

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

2.) 将 iVars 放在不带花括号的 @implementation 下

3.) 将 iVars 放在 @implementation 上方的私有接口中(类扩展)

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

我应该走哪条路?

编辑:我在这里只谈论 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

5 回答 5

163

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

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

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

选项 0:在@interface(不要这样做)

首先,让我们回顾一下为什么我们不想将实例变量放在@interface声明中。

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

  2. 将实例变量放入 an@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

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

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

这是我们需要类扩展的地方。我们可以将可重用视图管理 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方法调用该方法来初始化可重用视图管理代码。我们将从-[UICollectionView initWithFrame:collectionViewLayout:]in调用该方法UICollectionView.m,并在 in 中实现它UICollectionView+ReusableViews.m

于 2012-11-26T21:12:25.423 回答
5

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

选项 1 和 3 基本相同。这完全没有区别。

选择是将实例变量放在头文件还是实现文件中。使用头文件的好处是您有一个快速简便的键盘快捷键(Xcode 中的 Command + Control + Up)来查看和编辑您的实例变量和接口声明。

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

实现实例变量在某些情况下是很好的选择,但对于我的大部分代码,我仍然将实例变量放在标题中,因为这对我作为在 Xcode 中工作的编码人员来说更方便。我的建议是做任何你觉得对你更方便的事情。

于 2012-11-26T20:11:32.083 回答
4

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

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

于 2012-11-26T14:31:46.420 回答
3

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

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

从文档中:

您可以定义没有属性的实例变量

最佳做法是在您需要跟踪值或另一个对象的任何时候对对象使用属性。

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

于 2012-11-26T14:30:59.337 回答
1

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

于 2012-11-26T14:31:07.623 回答