Declaration/definition of variables locations in ObjectiveC?

自从开始在 iOS 应用程序和目标 C 上工作以来,我一直对可以声明和定义变量的不同位置感到困惑。一方面,我们有传统的 C 方法,另一方面,我们有新的 ObjectiveC 指令,它在此基础上添加了 OO。你们能帮助我了解最佳实践和情况,我想使用这些位置为我的变量,也许正确我目前的理解?

下面是一个示例类(. h 和. m) :

#import <Foundation/Foundation.h>


// 1) What do I declare here?


@interface SampleClass : NSObject
{
// 2) ivar declarations
// Pretty much never used?
}


// 3) class-specific method / property declarations


@end

还有

#import "SampleClass.h"


// 4) what goes here?


@interface SampleClass()


// 5) private interface, can define private methods and properties here


@end


@implementation SampleClass
{
// 6) define ivars
}


// 7) define methods and synthesize properties from both public and private
//    interfaces


@end
  • 我对1和4的理解是,它们是 C 风格的基于文件的声明和定义,对类的概念一无所知,因此必须准确地使用它们在 C 中的用法。我以前见过它们用于实现基于静态变量的单例。我还漏掉了其他方便的用途吗?
  • 我从 iOS 的工作中得出的结论是,ivar 几乎已经完全被逐步淘汰,不再受到“合成”指令的限制,因此大多数情况下可以被忽略。是这样吗?
  • 关于5: 我为什么要在私有接口中声明方法?我的私有类方法似乎在没有接口声明的情况下编译得很好。主要是为了可读性吗?

谢谢大家!

57986 次浏览

我也是新来的,希望我不会搞砸什么。

1 & 4: C 风格的全局变量: 它们有广泛的文件范围。两者之间的区别在于,因为它们是文件宽的,所以第一个对于任何导入头文件的人都是可用的,而第二个则不是。

2: 实例变量。大多数实例变量都是通过使用属性的访问器合成和检索/设置的,因为它使内存管理变得美观和简单,并且提供了易于理解的点符号。

6: Implementation ivars are somewhat new. It's a good place to put private ivars, since you want to only expose what's needed in the public header, but subclasses don't inherit them AFAIK.

3 & 7: Public method and property declarations, then implementations.

5: 私有接口。我总是使用私有接口,只要我可以保持清洁,并创建一种黑盒效果。如果他们不需要知道,就放在那里。我这样做也是为了可读性,不知道还有没有其他原因。

I can understand your confusion. Especially since recent updates to Xcode and the new LLVM compiler changed the way ivars and properties can be declared.

在“ Modern”Objective-C (在“ old”Obj-C 2.0中)之前,您没有太多选择。实例变量通常在大括号 { }之间的头部声明:

// MyClass.h
@interface MyClass : NSObject {
int myVar;
}
@end

You were able to access these variables only in your implementation, but not from other classes. To do that, you had to declare accessor methods, that look something like this:

// MyClass.h
@interface MyClass : NSObject {
int myVar;
}


- (int)myVar;
- (void)setMyVar:(int)newVar;


@end




// MyClass.m
@implementation MyClass


- (int)myVar {
return myVar;
}


- (void)setMyVar:(int)newVar {
if (newVar != myVar) {
myVar = newVar;
}
}


@end

这样你也可以从其他类中获取并设置这个实例变量,使用通常的方括号语法来发送消息(调用方法) :

// OtherClass.m
int v = [myClass myVar];  // assuming myClass is an object of type MyClass.
[myClass setMyVar:v+1];

因为手动声明和实现每个访问器方法都很烦人,所以引入了 @property@synthesize来自动生成访问器方法:

// MyClass.h
@interface MyClass : NSObject {
int myVar;
}
@property (nonatomic) int myVar;
@end


// MyClass.m
@implementation MyClass
@synthesize myVar;
@end

The result is much clearer and shorter code. The accessor methods will be implemented for you and you can still use the bracket syntax as before. But in addition, you can also use the dot syntax to access properties:

// OtherClass.m
int v = myClass.myVar;   // assuming myClass is an object of type MyClass.
myClass.myVar = v+1;

Since Xcode 4.4 you don't have to declare an instance variable yourself anymore and you can skip @synthesize too. If you don't declare an ivar, the compiler will add it for you and it will also generate the accessor methods without you having to use @synthesize.

自动生成的 ivar 的默认名称是以下划线开头的名称或属性。您可以使用 @synthesize myVar = iVarName;更改生成的 ivar 的名称

// MyClass.h
@interface MyClass : NSObject
@property (nonatomic) int myVar;
@end


// MyClass.m
@implementation MyClass
@end

这将与上面的代码完全一样工作。由于兼容性原因,您仍然可以在头中声明 ivar。但是,因为您希望这样做(而不是声明属性)的唯一原因是要创建一个私有变量,所以现在也可以在实现文件中这样做,这是首选方法。

实现文件中的 @interface块实际上是 分机,可用于转发声明方法(不再需要)和(重新)声明属性。例如,您可以在头中声明一个 readonly属性。

@property (nonatomic, readonly) myReadOnlyVar;

and redeclare it in your implementation file as readwrite to be able to set it using the property syntax and not only via direct access to the ivar.

As for declaring variables completely outside of any @interface or @implementation block, yes those are plain C variables and work exactly the same.

首先,阅读@DrummerB 的回答。这是一个很好的概述为什么和你应该一般做什么。记住这一点,回到你的具体问题:

#import <Foundation/Foundation.h>


// 1) What do I declare here?

这里没有实际的变量定义(如果您确切地知道自己在做什么,那么这样做在技术上是合法的,但千万不要这样做)。你可以定义几种其他的东西:

  • Typedefs
  • 枚举
  • 实习生

外部实现看起来像变量声明,但它们只是承诺在其他地方实际声明它。在 ObjecC 中,它们应该只用于声明常量,而且通常只用于字符串常量。例如:

extern NSString * const MYSomethingHappenedNotification;

然后在 .m文件中声明实际常数:

NSString * const MYSomethingHappenedNotification = @"MYSomethingHappenedNotification";

@interface SampleClass : NSObject
{
// 2) ivar declarations
// Pretty much never used?
}

正如 DrummerB 指出的,这是遗产。不要把任何东西放在这里。


// 3) class-specific method / property declarations


@end

是的。


#import "SampleClass.h"


// 4) what goes here?

外部常数,如上所述。文件静态变量也可以放在这里。这些是其他语言中类变量的等价物。


@interface SampleClass()


// 5) private interface, can define private methods and properties here


@end

是的


@implementation SampleClass
{
// 6) define ivars
}

但很少。几乎总是应该允许 clang (Xcode)为您创建变量。例外情况通常发生在非对象类型的变量(比如 Core Foundation 对象,特别是 C + + 对象,如果这是一个对象类型的话) ,或者存储语义奇怪的变量(比如由于某种原因与属性不匹配的变量)。


// 7) define methods and synthesize properties from both public and private
//    interfaces

一般来说,你不应该再这样合成了。 Clang (Xcode)会为你做这件事,你应该让它去做。

在过去的几年里,事情变得非常简单。副作用是现在有三个不同的时代(脆性 ABI,非脆性 ABI,非脆性 ABI + 自动合成)。因此,当您看到较旧的代码时,可能会有些困惑。因此,由简单性引起的混淆: D

这是在 Objective-C 中声明的各种变量的一个例子。变量名表示它的访问权限。

文件: Animal.h

@interface Animal : NSObject
{
NSObject *iProtected;
@package
NSObject *iPackage;
@private
NSObject *iPrivate;
@protected
NSObject *iProtected2; // default access. Only visible to subclasses.
@public
NSObject *iPublic;
}


@property (nonatomic,strong) NSObject *iPublic2;


@end

文件: Animal.m

#import "Animal.h"


// Same behaviour for categories (x) than for class extensions ().
@interface Animal(){
@public
NSString *iNotVisible;
}
@property (nonatomic,strong) NSObject *iNotVisible2;
@end


@implementation Animal {
@public
NSString *iNotVisible3;
}


-(id) init {
self = [super init];
if (self){
iProtected  = @"iProtected";
iPackage    = @"iPackage";
iPrivate    = @"iPrivate";
iProtected2 = @"iProtected2";
iPublic     = @"iPublic";
_iPublic2    = @"iPublic2";


iNotVisible   = @"iNotVisible";
_iNotVisible2 = @"iNotVisible2";
iNotVisible3  = @"iNotVisible3";
}
return self;
}


@end

注意,iNotVisible 变量在任何其他类中都不可见。这是一个可见性问题,所以用 @property@public声明它们并不会改变它。

Inside a constructor it's good practice to access variables declared with @property using underscore instead self to avoid side effects.

让我们试着访问变量。

文件: Cow.h

#import "Animal.h"
@interface Cow : Animal
@end

文件: Cow.m

#import "Cow.h"
#include <objc/runtime.h>


@implementation Cow


-(id)init {
self=[super init];
if (self){
iProtected    = @"iProtected";
iPackage      = @"iPackage";
//iPrivate    = @"iPrivate"; // compiler error: variable is private
iProtected2   = @"iProtected2";
iPublic       = @"iPublic";
self.iPublic2 = @"iPublic2"; // using self because the backing ivar is private


//iNotVisible   = @"iNotVisible";  // compiler error: undeclared identifier
//_iNotVisible2 = @"iNotVisible2"; // compiler error: undeclared identifier
//iNotVisible3  = @"iNotVisible3"; // compiler error: undeclared identifier
}
return self;
}
@end

我们仍然可以使用运行库访问不可见的变量。

档案: Cow.m (Part 2)

@implementation Cow(blindAcess)


- (void) setIvar:(NSString*)name value:(id)value {
Ivar ivar = class_getInstanceVariable([self class], [name UTF8String]);
object_setIvar(self, ivar, value);
}


- (id) getIvar:(NSString*)name {
Ivar ivar = class_getInstanceVariable([self class], [name UTF8String]);
id thing = object_getIvar(self, ivar);
return thing;
}


-(void) blindAccess {
[self setIvar:@"iNotVisible"  value:@"iMadeVisible"];
[self setIvar:@"_iNotVisible2" value:@"iMadeVisible2"];
[self setIvar:@"iNotVisible3" value:@"iMadeVisible3"];
NSLog(@"\n%@ \n%@ \n%@",
[self getIvar:@"iNotVisible"],
[self getIvar:@"_iNotVisible2"],
[self getIvar:@"iNotVisible3"]);
}


@end

Let's try to access the not visible variables.

文件: main.m

#import "Cow.h"
#import <Foundation/Foundation.h>
int main(int argc, char *argv[]) {
@autoreleasepool {
Cow *cow = [Cow new];
[cow performSelector:@selector(blindAccess)];
}
}

这个指纹

iMadeVisible
iMadeVisible2
iMadeVisible3

注意,我能够访问支持的 ivar _iNotVisible2,它对子类是私有的。在 Objective-C 中,所有的变量都可以读取或设置,即使是那些标记为 @private的变量也不例外。

I didn't include associated objects or C variables as they are different birds. As for C variables, any variable defined outside @interface X{} or @implementation X{} is a C variable with file scope and static storage.

我没有讨论内存管理属性,或者 readonly/readwrite、 getter/setter 属性。