将 iVar 放在“现代”Objective-C 中的哪个位置?

Ray Wenderlich 所著的“ iOS6 by Tutorials”一书中有一章非常精彩地讲述了如何编写更“现代”的 Objective-C 代码。书中有一节描述了如何将 iVars 从类的头部移动到实现文件中。 因为所有的 iVar 都应该是私有的,所以这似乎是正确的做法。

但是到目前为止,我找到了3种方法,每个人的做法都不一样。

1.)将 iVars 放在大括号内的@实现下(书中是这样做的)。

2. 将 iVars 置于@实现之下,不要使用大括号

3. 将 iVars 放在@实现(类扩展)之上的私有接口中

所有这些解决方案似乎都工作得很好,到目前为止,我还没有注意到应用程序的行为有什么不同。 我想没有“正确”的方法可以做到这一点,但我需要编写一些教程,我想只选择一种方法来完成我的代码。

我该走哪条路?

编辑: 我在这里只是谈论 iVar。不是财产。只有对象本身需要的额外变量,而且不应该向外部公开。

代码示例

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
14387 次浏览

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

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

根据文件:

可以不使用属性定义实例变量

最佳实践是在需要跟踪某个值或另一个对象时对该对象使用属性。

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

公共 ivar 实际上应该在@接口中声明属性(可能就是您在1中想到的)。如果您正在运行最新的 Xcode 并使用现代运行时(64位 OS X 或 iOS) ,私有 ivar 可以在@实现(2)中声明,而不是在类扩展中声明,这可能就是您在3中想到的。

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

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

选项2完全错了,那些是全局变量,不是实例变量。

选项1和选项3本质上是相同的,完全没有区别。

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

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

实现实例变量在某些情况下是很好的选择,但是对于我的大部分代码,我仍然把实例变量放在头部,只是因为它对于我这个在 Xcode 工作的程序员来说更方便。我的建议是做你觉得对你更方便的事情。

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

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

因此,让我们假设您只编写使用现代运行时的应用程序。你应该把你的静脉注射器放在哪里?

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

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

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

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

所以我们不想把实例变量放在 @interface中,我们应该把它们放在哪里呢?

选项2: 在 @implementation中不使用大括号(不要这样做)

接下来,让我们讨论一下选项2,“将 iVars 置于@实现之下,不要使用大括号”。这样做 没有声明实例变量!你说的是这个:

@implementation Person


int age;
NSString *name;


...

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

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

那就只剩下选项1和选项3了。

选项1: 在 @implementation中使用大括号(照做)

通常我们想使用选项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.m中实现主要的 UICollectionView方法,并且我们可以在 UICollectionView+ReusableViews.m中实现管理可重用视图的方法,这使得我们的源代码更易于管理。

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

这就是我们需要类扩展的地方。我们可以将可重用的视图管理变量放在一个私有头文件的类扩展中:

// UICollectionView_ReusableViewsSupport.h


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


NSMutableDictionary *registeredSupplementaryViewSources;
NSMutableDictionary *spareSupplementaryViewsByIdentifier;
}


- (void)initReusableViewSupport;


@end

我们不会将这个头文件发送给库的用户。我们将它导入到 UICollectionView.mUICollectionView+ReusableViews.m中,这样 需求看到这些变量的所有东西都能看到它们。我们还引入了一个方法,我们希望主 init方法调用该方法来初始化可重用视图管理代码。我们将在 UICollectionView.m中从 -[UICollectionView initWithFrame:collectionViewLayout:]调用该方法,并在 UICollectionView+ReusableViews.m中实现它。