关于iphone:Objective C中变量位置的声明/定义?

Declaration/definition of variables locations in ObjectiveC?

自从开始在iOS应用程序和目标C上工作以来,我真的很困惑于人们可以在不同的位置声明和定义变量。一方面,我们有传统的C方法,另一方面,我们有新的客观主义指令,在上面添加OO。你们能帮助我了解最佳实践和我想用这些位置作为变量的情况吗?也许能纠正我目前的理解。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
#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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#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的看法是,在@synthesis指令之外,ivar已经被完全淘汰,因此可以忽略不计。是这样吗?
  • 关于5:我为什么要在私有接口中声明方法?我的私有类方法在接口中没有声明的情况下似乎编译得很好。它主要是为了可读性吗?

谢谢大家!


我能理解你的困惑。特别是最近对xcode和新的llvm编译器的更新改变了ivar和属性的声明方式。

在"现代"目标C(在"旧"的obj-c 2.0中)之前,你没有很多选择。用于在大括号{ }之间的头中声明的实例变量:

1
2
3
4
5
// MyClass.h
@interface MyClass : NSObject {
    int myVar;
}
@end

您只能在实现中访问这些变量,而不能从其他类中访问。为此,您必须声明访问器方法,其外观如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
// MyClass.h
@interface MyClass : NSObject {
    int myVar;
}
@property (nonatomic) int myVar;
@end

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

结果是代码更加清晰和简短。访问器方法将为您实现,您仍然可以像以前一样使用括号语法。此外,还可以使用点语法访问属性:

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

由于Xcode4.4,您不再需要自己声明实例变量,也可以跳过@synthesize。如果您不声明一个ivar,编译器将为您添加它,它也将生成访问器方法,而不必使用@synthesize

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

1
2
3
4
5
6
7
8
// MyClass.h
@interface MyClass : NSObject
@property (nonatomic) int myVar;
@end

// MyClass.m
@implementation MyClass
@end

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

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

1
@property (nonatomic, readonly) myReadOnlyVar;

并在实现文件中将其重新声明为readwrite,以便能够使用属性语法设置它,而不仅仅是通过直接访问ivar。

对于完全声明任何@interface@implementation块之外的变量,是的,这些是普通的C变量,工作原理完全相同。


首先,阅读@drummerb的答案。这是一个很好的概述为什么和你通常应该做什么。考虑到这一点,针对您的具体问题:

1
2
3
#import <Foundation/Foundation.h>

// 1) What do I declare here?

这里没有实际的变量定义(如果你确切知道自己在做什么,但从来没有这样做,那么这样做在技术上是合法的)。您可以定义其他几种类型的内容:

  • 打字机
  • 枚举
  • 外来者

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

1
extern NSString * const MYSomethingHappenedNotification;

然后在您的.m文件中声明实际常量:

1
NSString * const MYSomethingHappenedNotification = @"MYSomethingHappenedNotification";
1
2
3
4
5
@interface SampleClass : NSObject
{
    // 2) ivar declarations
    // Pretty much never used?
}

正如鼓手所指出的,这是遗产。别把东西放在这里。

1
2
3
// 3) class-specific method / property declarations

@end

是的。

1
2
3
#import"SampleClass.h"

// 4) what goes here?

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

1
2
3
4
5
@interface SampleClass()

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

@end

是的

1
2
3
4
@implementation SampleClass
{
    // 6) define ivars
}

但很少。几乎总是应该允许clang(xcode)为您创建变量。异常通常是围绕非ObjiVIAR(像核心基础对象,尤其是C++对象,如果这是Objc++类),或者IVARs具有奇怪的存储语义(像IVARS,由于某些原因与属性不匹配)。

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

一般来说,你不应该再@synthesis了。clang(xcode)会帮你做的,你应该让它做。

在过去的几年里,事情变得非常简单。副作用是现在有三个不同的时代(易碎ABI,非易碎ABI,非易碎ABI+自动合成)。所以当您看到旧代码时,可能会有点混乱。因此,由简单性引起的混乱:d


我也是个新手,所以希望我不会搞砸任何事情。

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

2:实例变量。大多数实例变量都是使用属性通过访问器进行合成和检索/设置的,因为它使内存管理变得简单易行,并且使您易于理解点表示法。

6:实现ivar有些新。这是一个放置private-ivar的好地方,因为您只想公开公共头段中需要的内容,但是子类不能继承它们afaik。

3&7:公共方法和属性声明,然后是实现。

5:私有接口。我总是在任何时候都使用私有接口来保持事物的整洁并创建一种黑盒效果。如果他们不需要知道,把它放在那里。我这样做也是为了可读性,不知道还有什么其他的原因。


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

文件:动物

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@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

文件:动物。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#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

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

在构造函数内部,访问使用@property声明的变量(使用下划线而不是self)以避免副作用是很好的做法。

让我们尝试访问变量。

文件:牛

1
2
3
#import"Animal.h"
@interface Cow : Animal
@end

文件:奶牛

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#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(第2部分)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@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(@"
%@
%@
%@"
,
          [self getIvar:@"iNotVisible"],
          [self getIvar:@"_iNotVisible2"],
          [self getIvar:@"iNotVisible3"]);
}

@end

让我们尝试访问不可见的变量。

文件:主M

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

这张照片

1
2
3
iMadeVisible
iMadeVisible2
iMadeVisible3

请注意,我能够访问子类私有的支持ivar _iNotVisible2。在objective-c中,所有变量都可以被读取或设置,甚至那些标记为@private的变量也不例外。

我没有包括相关的对象或C变量,因为它们是不同的鸟。对于c变量,在@interface X{}@implementation X{}之外定义的任何变量都是具有文件范围和静态存储的c变量。

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