在.m文件中,@implementation和@interface下声明变量的区别

7

我已经学习了一段时间的Objective-C。从我的学习中,我知道当你在.h文件@interface里声明一个变量时,这个变量可以被公开访问(类似于Java中的public变量)。

@interface MyObject

@property NSInteger intData;

@end

但是,如果你在.m文件中的@interface内部声明它,它只能在@implementation内部的.m文件中访问,除非你为它提供getter和setter。

@interface MyObject ()

@property NSInteger intData;

@end

但我还注意到另一种声明变量的方式,即在@implementation下声明它。

@implementation

NSInteger intData;

@end

我看到在.m文件中使用@implementation声明与使用@interface和@property声明的方式相同,但我不明白两者之间的区别。请注意,我已经在Stack Overflow上搜索过此问题,但是所有的讨论都是关于在.h文件中使用@implementation和@interface之间的区别,所以我认为这不是重复的问题。

3
@interface中声明的NSInteger是一个全局变量。它仅仅是一个变量,没有任何属性(访问器方法、KVC等)所具有的特性。 - Rob
2个回答

6

首先,你不是在声明一个变量,而是在声明一个属性。属性由实例变量支持,但也添加了方法。这里解释一下放置变量的位置:

@interface MyClass : NSObject {
    NSInteger i ;
}
@end

这是在类中放置实例变量的位置。它只能被类和分类中的方法访问。(旁注:它可以被外部访问,但这不是推荐的做法) 另一个例子:
@interface MyClass : NSObject
@end

@implementation MyClass {
    NSInteger i ;
}
@end

这也是实例变量,但只能被编写在该代码块内的方法所访问。(顺便说一下:可以通过查看类定义来访问它,但这不是推荐(或常见)的做法)

另一个例子:

@interface MyClass : NSObject
@property NSInteger i ;
@end

与下面的代码等价:

@interface MyClass : NSObject {
    NSInteger _i ; // you are not allowed to access it by this variable
}
- (NSInteger) i ;
- (void) setI:(NSInteger)value ;
@end

这是一个允许人们获取和设置的属性。您可以在自己的方法或其他方法中使用该变量,例如:

NSLog ( @"The value is %i" , self.i ) ; // if it's your instance method
NSLog ( @"The value is %i" , object.i ) ; // if it's object's instance method

另一个例子:
@interface MyClass : NSObject {
    NSInteger i ;
}
@property NSInteger i ;
@end
@implementation MyClass
@synthesize i ; // Causes the property to line up with the ivar by the same name.
@end

等同于:

@interface MyClass : NSObject {
    NSInteger i ; // you ARE allowed to use this since you defined it
}
- (NSInteger) i ;
- (void) setI:(NSInteger)value ;
@end

在这里,您可以使用getter/setter方法或实例变量本身。但是,通常应该使用方法,因为您已[隐式地]将它们声明为原子的,因此它们具有线程同步功能。如果你想让它不做线程处理(并且加快速度,只要不在多线程环境中使用):

@property (nonatomic) NSInteger i ;
@property (nonatomic,readonly) NSInteger i ; // only makes a getter method

我建议你在一段时间内避免使用这个,而是直接使用属性,因为这样可以帮助你避免很多常见的错误。除非你对程序进行了剖析,并确定这是导致性能损失的原因,否则你应该只使用属性。

另一个例子:

@interface MyClass : NSObject
@end

@implementation MyClass
NSInteger i ;
@end

这不是实例变量,而是一个全局变量,恰巧在你的@implementation作用域中编写。

如上所述,如何将其转换为实例变量(即将其放入括号中)。

还有一点需要注意:

声明属性的方式如下:

@interface MyClass ()
@property NSInteger i ;
@end

这并没有使其变成私有属性。然而,它被隐藏在一些普通人无法访问的文件中,所以编译器并不知道该属性的存在。

你代码中的其他函数仍然可以调用:

[yourObject i] ;

要获取该属性的值,但首先他们必须知道它存在。
附加说明以回答评论中的问题:
默认情况下,属性是原子性的。它不一定遵循原子性的严格定义(我建议您现在不要看这个),但具有相同的效果:线程保证能够看到完整和最新的值,无论另一个线程何时写入它。通常在合成getter/setter方法时实现这一点。
- (NSInteger) i {
    @synchronized(self) {
        return i ;
    }
}
- (void) setI:(NSInteger)value {
    @synchronized(self) {
        i = value ;
    }
}

如果您指定nonatomic,它将合成以下内容:
- (NSInteger) i {
    return i ;
}
- (void) setI:(NSInteger)value {
    i = value ;
}

如果您的属性是atomic,那么您不应该直接访问实例变量。这样做会违反您最初为其提供的线程保护。(附注:有些情况下可以这样做,但在尝试之前请等待您更熟悉线程/同步。)

1
@EpicNinja - 我在底部增加了一些额外的内容,描述了atomicnonatomic的作用。 - iAdjunct
2
这个答案有几个问题。1) 第一个代码示例不应该使用。没有理由在公共头文件中声明私有实例变量。 2) 第二个代码示例声明了一个私有实例变量。您的语句“可以通过在该块内编写的方法访问”有点令人困惑。这样的变量可以被类的任何实例方法访问。 3) 您应该提到,在@implementation行内部的全局变量声明可以通过使用花括号简单地变成一个适当的实例变量。 - rmaddy
1
你的代码中其他地方仍然可以调用以下函数:[yourObject i]; 来获取该属性的值 - 但是它们必须知道它存在。完全清楚,编译器将强制执行此属性的私有性:它不允许您在未导入声明的位置调用该方法。 - jscs
@JoshCaswell - 不一定。有许多方法可以做到这一点(为您使用的声明另一个类别具有该方法; 检查类定义; 调用[object performSelector:@selector(i)]; 等)。编译器将强制执行普通情况,其中它不可见,但这比“私有”更“隐藏”。 - iAdjunct
@rmaddy - 老实说,我不确定这个范围是什么(同一文件还是@implementation/@end),所以我就把它留在那里了。然而,在同一个文件中放置多个@implementation块是很少见的,因此在开始做更有创意的事情之前,这种差异是无关紧要的。 - iAdjunct
显示剩余3条评论

4
当你在@interface MyObject (){}中声明属性时,你在所谓的匿名或类别中声明它。因为它是在.m文件中声明的,所以只有该文件内部可以看到它(没有其他类可以看到)。但是你也可以在.h文件中声明这个类别,这样它就可以被其他类看到了。
@implementation部分中声明NSInteger intData实际上并没有声明一个实例变量,而是声明了一个全局变量,这意味着它只有一个实例,并且被整个应用程序共享,或者说,被你的类的所有实例(因为它是唯一知道这个全局变量的类)共享。

3
被称为“匿名”或类别类,或称为“类扩展”(参见[Class Extensions Extend the Internal Implementation](https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html#//apple_ref/doc/uid/TP40011210-CH6-SW3))。 - Rob
1
全局变量可以通过添加花括号简单地转换为实例变量。 - rmaddy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接