在ObjectiveC中声明/定义变量位置?


113

自从开始在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一起工作的收获是,ivars已在@synthesize指令之外完全被淘汰,因此可以忽略不计。是这样吗
  • 关于5:为什么我要在私有接口中声明方法?我的私有类方法似乎可以在接口中没有声明的情况下进行编译。主要是为了提高可读性吗?

谢谢大家!

Answers:


154

我能理解你的困惑。特别是由于最近对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的默认名称是名称或您的属性以下划线开头。您可以使用来更改生成的ivar的名称@synthesize myVar = iVarName;

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

// MyClass.m
@implementation MyClass
@end

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

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

@property (nonatomic, readonly) myReadOnlyVar;

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

至于完全在any @interface@implementationblock 之外声明变量,是的,它们是普通C变量,并且工作原理完全相同。


2
好答案!另外值得注意:stackoverflow.com/questions/9859719/...
nycynik

44

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

#import <Foundation/Foundation.h>

// 1) What do I declare here?

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

  • 伤寒
  • 枚举
  • 外部

外部变量看起来像变量声明,但是它们只是在其他地方实际声明它的承诺。在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 ivar(例如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


只是想知道,但是为什么我们不应该明确地合成呢?之所以这样做,是因为我发现我的代码更易于理解,特别是当某些属性具有综合访问器而某些属性具有自定义实现时,因为我习惯于进行综合。显式合成有什么弊端吗?
Metabble 2012年

将其用作文档的问题在于它实际上并未记录任何内容。尽管使用了合成,但您可能已经覆盖了一个或两个访问器。无法从合成线中分辨出真正有用的东西。比没有文档更糟糕的是,误导性文档。别说了。
Rob Napier 2012年

3
为什么#6稀有?这不是获取私有变量的最简单方法吗?
pfrank

#5是获得私有财产的最简单,最好的方法。
罗布·纳皮尔2013年

1
@RobNapier有时仍然需要使用@ synthesize(例如,如果属性是只读的,其访问器将被覆盖)
Andy

6

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

1和4:C样式的全局变量:它们具有文件范围的作用域。两者之间的区别在于,由于它们的文件范围很广,因此第一个将对任何导入标头的人可用,而第二个则不能。

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

6:实现错误有些新。这是放置私有ivars的好地方,因为您只想公开公共标头中需要的内容,但子类不会继承AFAIK。

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

5:专用接口。只要有可能,我总是使用私有接口来保持环境整洁并创建一种黑盒效果。如果他们不需要了解它,请放在那里。我也这样做是出于可读性,不知道是否还有其他原因。


1
不要以为您搞砸了:)一些注释-#1&#4 esp和#4通常会看到静态存储变量。#1通常会看到指定的外部存储,然后是#4中分​​配的实际存储。#2)通常仅在子类出于任何原因需要它时。#5不再需要转发声明私有方法。
卡尔·威阿谢

是的,我自己检查了向前申报。如果一个私有方法在其后定义而没有前向声明,它曾经发出警告,对吧?当它没有警告我时,我感到很惊讶。
Metabble 2012年

是的,它是编译器的新组成部分。他们最近确实取得了很大进步。
卡尔·威阿谢

6

这是在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

请注意,我能够访问_iNotVisible2该子类专用的后备ivar 。在Objective-C中,可以读取或设置所有变量,即使是已标记的变量@private也不例外。

我没有包括关联的对象或C变量,因为它们是不同的鸟。至于C变量,任何在外部定义的变量@interface X{}@implementation X{}具有文件作用域和静态存储的C变量。

我没有讨论内存管理属性,也没有讨论只读/读写,getter / setter属性。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.