将iVars放在“现代”Objective-C中的哪里?

时间:2012-11-26 14:26:35

标签: objective-c

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

但到目前为止,我找到了3种方法。每个人都采用不同的方式。

1。)将@implementantion下的iVars置于一个花括号内(这就是本书中的方法)。

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

5 个答案:

答案 0 :(得分:154)

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

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

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

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

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

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

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

  3. 因此我们不希望将实例变量放在@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中可重用视图的方法,这使我们的源代码更易于管理。

    但是我们的可重用视图管理代码需要一些实例变量。这些变量必须暴露给@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
    

    我们不会将此头文件发送给我们库的用户。我们只需在UICollectionView.mUICollectionView+ReusableViews.m中导入它,以便需要查看这些ivars的所有内容都可以看到它们。我们还抛出了一个方法,我们希望主init方法调用初始化可重用视图管理代码。我们将在-[UICollectionView initWithFrame:collectionViewLayout:]的{​​{1}}中调用该方法,我们将在UICollectionView.m中实现该方法。

答案 1 :(得分:5)

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

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

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

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

实现实例变量对于某些情况来说是很好的选择,但是对于我的大多数代码,我仍然将实例变量放在标题中,因为它对于我作为在Xcode中工作的编码器来说更方便。我的建议是做你认为对你更方便的事情。

答案 2 :(得分:4)

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

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

答案 3 :(得分:3)

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

要阅读的文档是Programming in Objective-C指南。

来自文档:

  

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

     

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

     

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

答案 4 :(得分:1)

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