目标C中变量位置的声明/定义?

时间:2012-09-28 01:11:58

标签: iphone objective-c ios ios5

自从开始研究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中的使用方式.I'我们之前看到它们用于实现基于静态变量的单例。我还缺少其他方便用途吗?
  • 我从iOS工作中得到的结论是,在@synthesize指令之外,ivars已被完全淘汰,因此可能会被忽略。是这样的吗?
  • 关于5:为什么我要在私有接口中声明方法?我的私有类方法似乎在没有接口声明的情况下编译得很好。它主要是为了可读性吗?

非常感谢,伙计们!

4 个答案:

答案 0 :(得分:144)

我能理解你的困惑。特别是因为最近对Xcode和新LLVM编译器的更新改变了ivars和属性的声明方式。

在“现代”Objective-C之前(在“旧”Obj-C 2.0中)你没有很多选择。实例变量曾在大括号{ }

之间的标题中声明
// MyClass.h
@interface MyClass : NSObject {
    int myVar;
}
@end

您只能在实现中访问这些变量,而不能从其他类访问这些变量。要做到这一点,你必须声明访问器方法,看起来像这样:

// 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

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

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

从Xcode 4.4开始,您不必再自己声明实例变量,也可以跳过@synthesize。如果你没有声明一个ivar,编译器会为你添加它,它也会生成访问器方法,而你不必使用@synthesize

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

更改生成的ivar名称
// MyClass.h
@interface MyClass : NSObject 
@property (nonatomic) int myVar;
@end

// MyClass.m
@implementation MyClass
@end

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

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

@property (nonatomic, readonly) myReadOnlyVar;

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

至于完全在任何@interface@implementation块之外声明变量,是的,那些是纯C变量并且工作完全相同。

答案 1 :(得分:40)

首先,阅读@ DrummerB的回答。它很好地概述了为什么以及您通常应该做什么。考虑到这一点,针对您的具体问题:

#import <Foundation/Foundation.h>

// 1) What do I declare here?

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

  • typdefs
  • 枚举
  • 实习医生

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

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)为你创建变量。例外情况通常围绕非ObjC ivars(如Core Foundation对象,尤其是C ++对象,如果这是一个ObjC ++类),或者是具有奇怪存储语义的ivars(例如由于某些原因与属性不匹配的ivars)。


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

通常你不应该@synthesize了。 Clang(Xcode)会为你做,你应该让它。

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

答案 2 :(得分:6)

我也很新,所以希望我不要搞砸任何东西。

1&amp; 4:C风格的全局变量:它们具有文件范围。两者之间的区别在于,因为它们是文件范围的,所以第一个将可用于导入标题的任何人,而第二个不是。

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

6:实施ivars有点新。这是放置私有ivars的好地方,因为你只想暴露公共标题中需要的东西,但是子类不会继承它们AFAIK。

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

5:私人界面。每当我能保持干净并创造一种黑盒效果时,我总是使用私有接口。如果他们不需要知道它,就把它放在那里。我也这样做是为了可读性,不知道是否还有其他原因。

答案 3 :(得分:5)

这是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声明这些问题不会改变它。

在构造函数中,最好使用下划线@property来访问使用self声明的变量,以避免副作用。

让我们尝试访问变量。

档案: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(第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

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

档案: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的变量,没有例外。

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

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