Objective-C实例变量?

50

我确定我的困惑只是因为我被困在“Java思维模式”中,无法理解Obj-C在这种情况下的不同之处。

在Java中,我可以在类中声明一个变量,就像这样,每个该类的实例都会有自己的变量:

MyClass {

  String myVar;

  MyClass() {
    // constructor
  }
}

在 Obj-C 中,我尝试通过在 .m 文件中仅声明变量来做同样的事情,就像这样:

#import "MyClass.h"

@implementation MyClass

NSString *testVar;

@end

我在这里的期望是,这个变量的作用域限于这个类。因此,我创建了第二个相同的类:

#import "MySecondClass.h"

@implementation MySecondClass

NSString *testVar;

@end

我看到的(让我感到困惑的)是,更改一个类中的变量会影响到其他类中的值。实际上,如果我设置断点,然后“跳转到变量的定义”,它会把我带到那里。

我创建了一个非常小的Xcode项目,演示了这个问题,在这里


1
Objective-C更像C/C++而不是Java。Java相对独特的地方在于它没有单独的声明和实现文件,而是把所有内容都放在一个文件中。在Objective-C中,您可以在.h文件的@interface部分声明实例字段。 - Hot Licks
4个回答

142

改成这样:

@implementation MyClass

NSString *testVar;

@end
至:
@implementation MyClass {
    NSString *testVar;
}

// methods go here

@end

并且您将获得您所期望的内容。

正如您当前所写的代码,您实际上正在创建一个全局变量。由于连接器将两个全局变量合并为一个,因此在设置其中一个变量时,两者都会改变。使用花括号中的变量将成为一个适当的(且私有的)实例变量。

编辑:在被无缘无故地投票后,我想指出“旧”的做法和新的做法。

旧的方式:

SomeClass.h

@interface SomeClass : UIViewController <UITextFieldDelegate> {
    UITextField *_textField;
    BOOL _someBool;
}

@property (nonatomic, assign) BOOL someBool;

// a few method declarations

@end

SomeClass.m

@implementation SomeClass

@synthesize someBool = _someBool;

// the method implementations

@end

现在,使用现代的Objective-C编译器的全新改进方式:

SomeClass.h

@interface SomeClass : UIViewController

@property (nonatomic, assign) BOOL someBool;

// a few method declarations

@end

SomeClass.m

@interface SomeClass () <UITextFieldDelegate>
@end

@implementation SomeClass {
    UITextField *_textField;
}

// the method implementations

@end

新的方式有几个优点。主要的优点是,与类相关的所有实现细节都不会出现在.h文件中。客户端没有必要知道实现需要哪些代理。客户端也不需要知道我使用了哪些实例变量(ivars)。现在,如果实现需要一个新的ivar或者需要使用一个新的协议,.h文件不会改变。这意味着需要重新编译的代码更少。它更加清晰和高效。这也使得编辑更加容易。当我在编辑.m文件,并意识到我需要一个新的ivar时,在同一个正在编辑的.m文件中进行更改即可。无需来回切换。

还请注意,实现不再需要为属性使用ivar或者@synthesize了。


我猜是因为你没有描述@interface/@implementation的规范形式。虽然技术上允许你这样做,但几乎从未使用过,对新手来说可能会造成灾难性的困惑。 - Hot Licks
1
@HotLicks,我展示的是新的首选方法。现在没有理由再将私有变量放在.h文件中了,因为现代LLVM编译器已经不需要了。初学者应该学习新的方式,而不是旧的方式。 - rmaddy
@HotLicks 我编辑了我的答案,指出了编写此代码的旧方法和新方法。 - rmaddy
你知道你的新SomeClass.m和下面的代码有什么区别吗? @interface SomeClass () <UITextFieldDelegate> @property UITextField *_textField; @end @implementation SomeClass { } // 方法实现 @end - imcc
@maddy,全局变量在实现中的声明(如问题和此答案的第一个代码块中所示)与在实现中声明属性(不允许)之间有什么区别? - dant

0
你可能想要的是(除非你使用的是非常老的操作系统和编译器),只需使用属性语法即可。例如:
@interface MyClass : NSObject

// method declarations here ...

@property (copy) NSString*    myVar;

// ... or here.

@end

这将实现您想要的功能。它将隐式地合成一个实例变量和一个getter/setter对于此变量。如果您手动想要创建实例变量(通常不需要,除非您需要在非常旧的MacOS版本上运行代码),那么上面的代码在幕后执行以下操作来创建ivar:

@interface MyClass : NSObject
{
    NSString*    _myVar;
}

// method declarations here.

@end

注意花括号,这告诉编译器这不仅仅是在方法之间的全局变量,而是属于此对象的实例变量。

如果您只为内部使用创建属性,并且不希望类的客户端干扰它,可以在除最早的ObjC编译器外的所有内容中将其隐藏起来,方法是使用类扩展,该扩展从头文件“继续”类声明,但可以单独放置(通常在实现文件中)。 类扩展看起来像没有名称的类别:

@interface MyClass ()

@property (copy) NSString*    myVar;

@end

你可以将属性声明放在其中,或者甚至是 ivar 声明(同样用花括号括起来)。你甚至可以在类接口中将相同的属性声明为 readonly,然后在扩展中重新声明它,但是作为 readwrite,这样客户端只能读取它,但是您的代码可以更改它。

请注意,如果您没有使用 ARC(也就是说,您已经关闭了默认的自动引用计数),则必须在 dealloc 方法中将所有属性设置为 nil(除非它们被设置为 weakassign)。

NB - 所有上述都是 @interface 部分。您的实际代码将放在单独的 @implementation 部分中。这样,您可以拥有头文件(.h),您可以将其交给类的客户端,只包含您打算使用的部分,并将实现细节隐藏在实现文件(.m)中,您可以在其中更改它们,而不必担心某人可能会意外使用它们并且会破坏其他代码。

PS - 请注意,您想要不可变的NSStrings和其他对象,但也存在可变的版本(即NSMutableString)应该始终是copy属性,因为这将把NSMutableString转换为NSString,以便外部没有人可以更改您下面的可变字符串。对于所有其他对象类型,通常使用strong(如果不是ARC,则使用retain)。对于您类的所有者(例如其委托),通常使用weak(如果不是ARC,则使用assign)。

-1
在Objective-C中,如果想将变量设为私有的,可以使用以下语法:
MyClass.h

@interface MyClass : NSObject{

      NSString* _myTestVar; // Declaration
}
@end

然后在实现类中这样引用它 MyClass.m

#import "MyClass.h";

@implementation MyClass

  -(void)initializieTheString
  {
     _myTestVar= @"foo"; //Initialization

  }

@end

2
不要在公共头文件中声明私有ivars。虽然有效,但这是一个不好的想法。请参阅我的答案获取更多详细信息。 - rmaddy

-1

在Java中

MyClass {

  String myVar;
  MyClass() {
    // constructor
  }
}

在Objective-C中

MyClass.h

@interface MyClass : NSObject{

      NSString* str; // Declaration
}
@end

MyClass.m

@implementation MyClass

  -(void)initializieTheString
  {
     //Defination 
  }

@end

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