【问题标题】:Declaration/definition of variables locations in ObjectiveC?Objective C中变量位置的声明/定义?
【发布时间】:2018-06-02 20:41:35
【问题描述】:

自从开始研究 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 的看法是,在 @synthesize 指令之外几乎完全淘汰了 ivars,因此几乎可以忽略不计。是这样吗?
  • 关于 5:我为什么要在私有接口中声明方法?我的私有类方法似乎编译得很好,没有在接口中声明。主要是为了可读性吗?

非常感谢,伙计们!

【问题讨论】:

    标签: iphone objective-c ios ios5


    【解决方案1】:

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

    1 & 4:C 风格的全局变量:它们具有文件范围。两者之间的区别在于,由于它们是文件宽的,第一个将可供任何导入标头的人使用,而第二个则不可用。

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

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

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

    5:私有接口。我总是尽可能使用私有接口来保持干净并创建一种黑盒效果。如果他们不需要知道它,就把它放在那里。我也是为了可读性做的,不知道有没有其他原因。

    【讨论】:

    • 不要以为你搞砸了任何事情 :) 一些 cmets - #1 & #4 esp with #4 通常你会看到静态存储变量。 #1 通常你会看到指定的外部存储,然后是 #4 中分配的实际存储。 #2) 仅当子类出于某种原因需要它时。 #5 不再需要转发声明私有方法。
    • 是的,我自己检查了前向声明。如果一个私有方法调用另一个在它之后定义的没有前向声明的私有方法,它曾经发出警告,对吗?当它没有警告我时,我有点惊讶。
    • 是的,它是编译器的新部分。他们最近确实取得了很大进步。
    【解决方案2】:

    我能理解你的困惑。尤其是最近对 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 变量,工作方式完全相同。

    【讨论】:

    【解决方案3】:

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

    #import <Foundation/Foundation.h>
    
    // 1) What do I declare here?
    

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

    • 类型定义
    • 枚举
    • 外部人员

    Extern 看起来像变量声明,但它们只是在其他地方实际声明它的承诺。在 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 对象,尤其是如果这是 ObjC++ 类的 C++ 对象),或具有奇怪存储语义的 ivars(如由于某种原因与属性不匹配的 ivars)。


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

    通常你不应该再@synthesize。 Clang (Xcode) 会为你做这件事,你应该让它去做。

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

    【讨论】:

    • 只是想知道,但为什么我们不应该明确地合成呢?我这样做是因为我发现我的代码更容易理解,尤其是当某些属性具有合成访问器并且某些具有自定义实现时,因为我习惯于合成。显式合成有什么缺点吗?
    • 将其用作文档的问题在于它并没有真正记录任何内容。尽管使用了综合,您可能已经覆盖了一个或两个访问器。没有办法从综合线中看出任何真正有用的东西。唯一比没有文档更糟糕的是误导性文档。别管它。
    • 为什么#6很少见?这不是获取私有变量的最简单方法吗?
    • 获得私有财产的最简单和最好的方法是#5。
    • @RobNapier 有时仍然需要使用@synthesize(例如,如果属性是只读的,则其访问器被覆盖)
    【解决方案4】:

    这是一个在 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 声明它们不会改变它。

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

    让我们尝试访问变量。

    文件: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 变量。

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

    【讨论】:

      猜你喜欢
      • 2010-09-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-03-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多