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

Tal*_*ode 77 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
Run Code Online (Sandbox Code Playgroud)

2)

#import "Person.h"

@implementation Person

int age;
NSString *name;


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

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
Run Code Online (Sandbox Code Playgroud)

rob*_*off 155

将实例变量放入@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. 将实例变量放入@interfacemake编译需要更长的时间,因为每当我们添加,更改或删除ivar声明时,我们都必须重新编译.m导入该接口的每个文件.

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

选项2:@implementation没有支撑(不要这样做)

接下来,让我们讨论你的选项2,"将iVars放在@implementantion下,不带花括号".这并没有声明实例变量!你在说这个:

@implementation Person

int age;
NSString *name;

...
Run Code Online (Sandbox Code Playgroud)

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

如果需要全局变量.m,可以在文件中定义全局变量,即使在您的文件中也是如此@implementation- 例如,因为您希望所有实例共享某些状态,如缓存.但是你不能使用这个选项来声明ivars,因为它没有声明ivars.(此外,通常应声明您的实现专用的全局变量,static以避免污染全局命名空间并冒链接时错误.)

这留下了你的选择1和3.

选项1:@implementation带括号(Do It)

通常我们想使用选项1:将它们放在主@implementation块中,用括号括起来,如下所示:

@implementation Person {
    int age;
    NSString *name;
}
Run Code Online (Sandbox Code Playgroud)

我们把它们放在这里,因为它保持它们的存在是私有的,防止我前面描述的问题,并且因为通常没有理由把它们放在类扩展中.

那么我们什么时候想要使用您的选项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
Run Code Online (Sandbox Code Playgroud)

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

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

这是我们需要类扩展的地方.我们可以将可重用视图管理ivars放在私有头文件中的类扩展中:

// UICollectionView_ReusableViewsSupport.h

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

    NSMutableDictionary *registeredSupplementaryViewSources;
    NSMutableDictionary *spareSupplementaryViewsByIdentifier;
}

- (void)initReusableViewSupport;

@end
Run Code Online (Sandbox Code Playgroud)

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

  • 选项3:在1)您希望使用ivars的属性强制执行时,即使在您自己的实现中也是可用的(@property int age),以及2)您想要覆盖只读公共属性(@property(readonly)int age)在您的标头中,允许您的代码在实现中以readwrite方式访问该属性(@property(readwrite)int age).除非有其他方法可以做到这一点,@rob mayoff? (4认同)

Dar*_*ren 5

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

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

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

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

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