【问题标题】:Using instance variables with Modern Runtime在 Modern Runtime 中使用实例变量
【发布时间】:2010-11-03 16:53:24
【问题描述】:

我在 Obj-c 和 Cocoa 方面有几年的经验,但现在才刚刚回到它以及 Obj-C 2.0 等方面的进步。

我试图了解现代运行时并声明属性等。让我有点困惑的是现代运行时中隐式创建 iVar 的能力。当然,这意味着在您的代码中,您应该始终使用 self.property 来访问该值。

但是,在 init* 和 dealloc(假设您没有使用 GC)方法中,我们应该直接使用 iVar(在当前运行时中)。

所以问题是:

  1. 我们是否应该在 init* 中使用属性访问器并在 Modern Runtime 中解除分配?

  2. 如果是这样,为什么会有所不同?仅仅是因为编译器看不到 iVar 吗?

  3. 如果我需要覆盖访问器,我是否仍可以访问将在运行时定义的 iVar,还是必须定义运行时将使用的实际 iVar?

  4. 再次,如果我可以访问合成的 iVar,为什么我不能继续对 init* 和 dealloc 方法执行此操作?

我多次阅读文档,但它们似乎对所有这些内容都有些模糊,我想确保我能很好地理解它,以便决定如何继续编码。

希望我的问题很清楚。


测试快速总结

  1. 如果在 legacy 中不声明 ivar,编译器完全不爽

  2. 如果您在旧版编译器中围绕 ivar 使用 #ifndef __OBJC2__ 很高兴,您可以直接使用 ivar 和作为属性

  3. 在现代运行时,您可以保留 ivar 未定义并作为属性访问

  4. 在现代运行时,尝试直接访问 ivar 而不声明会在编译期间出错

  5. @private 的 ivar 声明当然允许直接访问旧版和现代版的 ivar

现在真的没有给出一个干净的前进方式吗?

【问题讨论】:

  • 为了澄清那些可能会感到困惑的人,“现代运行时”是指新的 Objective-C 运行时,它可能由 iPhone 应用程序和 64 位进程使用10.5 或更高。任何在 32 位或 Tiger 或更早版本上运行的 Objective-C 代码都在“Legacy Runtime”下运行。
  • @Quinn - 我不知道 iPhone OS 使用现代运行时。太甜了。
  • @Quinn - 感谢您的澄清,我应该把它放在原始消息中。

标签: objective-c macos objective-c-runtime


【解决方案1】:

有类似信息的another SO question,但不是完全重复。

来自Objective-C 2.0 documentation 和引用自Mark Bessey's answer 的底线如下:

取决于运行时的行为存在差异(另请参阅“运行时差异”):

对于旧版运行时,实例变量必须已经在 @interface 块中声明。如果存在与属性具有相同名称和兼容类型的实例变量,则使用它,否则会出现编译器错误。

对于现代运行时,实例变量是根据需要合成的。如果同名的实例变量已经存在,则使用它。

我的理解如下:

您不应在 init*dealloc 方法中使用属性访问器,原因与您不应在旧版运行时中使用它们的原因相同:如果您稍后覆盖属性方法,它会使您面临潜在的错误,并且最终做了一些在init*dealloc 中不应该做的事情。

您应该能够同时合成 ivar 覆盖属性方法,如下所示:

@interface SomeClass
{
}
@property (assign) int someProperty;
@end

@implementation SomeClass
@synthesize someProperty; // this will synthesize the ivar
- (int)someProperty { NSLog(@"getter"); return someProperty; }
- (void)setSomeProperty:(int)newValue
{
    NSLog(@"setter");
    someProperty = newValue;
}
@end

这使我认为您也可以在 init*dealloc 方法中访问合成的 ivar。我能想到的唯一问题是@synthesize 行可能必须在源文件中init*dealloc 方法的定义之前出现。

最后,由于在接口中声明的 ivars 仍然有效,这仍然是您最安全的选择。

【讨论】:

  • 嗯,这个评论似乎与 Barry 在引用 Greg Parker 中提到的内容相矛盾,并且直觉上似乎是错误的(尽管直觉有时可能是废话:-))。看来私有 iVar 声明将是可行的方法。
  • 根据他的回答,Barry Wark 在这件事上的经验比我多。我会相信他的直觉胜过我的:)
【解决方案2】:

由于实例变量本身只能在现代运行时合成(并且必须在 32 位或 Leopard 之前的 @interface 中声明),因此同时声明 ivar 是最安全/最便携的

  • 我们是否应该在 init* 中使用属性访问器并在 Modern Runtime 中解除分配?

对于-init*,我的经验法则是“可能”,而对于-dealloc,“通常不会”。

初始化对象时,您要确保正确复制/保留 ivars 的值。除非属性的 setter 有一些副作用使其不适合初始化,否则一定要重用属性提供的抽象。

释放对象时,您希望释放所有 ivar 对象,但不存储新对象。一个简单的方法是将属性设置为 nil (myObject.myIvar = nil),它基本上调用 [myObject setMyIvar:nil]。由于 nil 的消息被忽略,因此没有危险。但是,当 [myIvar release];通常是您所需要的。一般来说,不要在解除分配的行为与设置变量不同的情况下使用属性(或直接使用 setter)。

我完全可以理解 eJames 反对在 init/dealloc 中使用属性访问器的论点,但另一方面是,如果您更改属性行为(例如,从保留更改为复制,或者只是分配而不保留)并且不要不要在 init 中使用它,反之亦然,行为也可能不同步。如果初始化和修改 ivar 的行为应该相同,请同时使用属性访问器。

  • 如果是这样,为什么会有所不同?只是因为编译器看不到 ivar 吗?

现代运行时更智能地处理类大小和布局,这就是为什么您可以更改 ivars 的布局而无需重新编译子类的原因。它还能够从相应属性的名称和类型中推断出您想要的 ivar 的名称和类型。 Objective-C 2.0 Runtime Programming Guide 有更多信息,但同样,我不知道那里解释的细节有多深。

  • 如果我需要覆盖访问器,我是否仍然可以访问将在运行时定义的 iVar,还是必须定义运行时将使用的实际 iVar?

我没有对此进行测试,但我相信您可以在代码中访问命名的 ivar,因为它确实必须被创建。我不确定编译器是否会抱怨,但我猜既然它会让你合成 ivar 而不会抱怨,它也很聪明,可以知道合成的 ivar 并让你按名称引用它。

  • 同样,如果我可以访问合成的 iVar,为什么我不能继续对 init* 和 dealloc 方法执行此操作?

您应该能够在分配实例后随时访问属性和/或 ivar。

【讨论】:

  • 不幸的是,文档并没有深入探讨这一点。这就是这里提出问题的原因。您对 iVar “必须创建”的观点虽然具有误导性。现代运行时可以更好地布局类结构的原因是因为它是在运行时完成的。因此,这些变量还不存在。不过,我想我会在 64 位应用程序中运行一些测试并报告。
【解决方案3】:

在当前 (OS X 10.5/GCC 4.0.1) 编译器中,您无法直接访问运行时合成的 ivars。一位 OS X 运行时工程师 Greg Parker 将其 this 列在 cocoa-dev 列表中(2009 年 3 月 12 日):

你不能在当前的编译器中。一种 未来的编译器应该解决这个问题。采用 明确的@private ivars 与此同时。 @private ivar 不应该 被视为合同的一部分—— 这就是@private 的意思,强制执行 通过编译器警告和链接器 错误。

为什么没有办法 显式声明实例变量 在新运行时的 .m 文件中?

三个原因:(1)有一些 重要的设计细节工作 出,(2)编译器工程师小时是 有限,并且 (3) @private ivars 是 总体来说还不错。

因此,现在您必须使用点符号来访问属性,即使在 initdealloc 中也是如此。这违背了在这些情况下直接使用 ivars 的最佳实践,但没有办法绕过它。我发现在大多数情况下,使用运行时合成的 ivars 的易用性(以及性能优势)超过了这一点。如果您确实需要直接访问 ivar,则可以按照 Greg Parker 的建议使用 @private ivar(没有什么可以阻止您混合显式声明的 ivar 和运行时合成的 ivar)。

更新 对于 OS X 10.6,64 位运行时确实允许通过 self->ivar 直接访问合成的 ivars。

【讨论】:

  • 谢谢巴里,这就是我的想法并希望得到证实。正如我在下面的评论中提到的,在真正标记为已回答之前,我将对此进行测试。
  • 好的,我刚刚确认这确实如此处所述。有关结果,请参见上面的编辑。
  • 看来您可以省略“self->”部分,也可以直接使用 ivar 名称。不过,目前这似乎不适用于 iPhone 运行时。
  • @briankc 太棒了。请记住,直接访问 ivars 将绕过 KVO 通知,因此它应该只在 init/dealloc 方法中使用。 iPhone 和 OS X 运行时经常相互跨越。 Apple 似乎采用了交替发布计划,自然而然地将最新的运行时放在一个平台上,然后再放在另一个平台上。
【解决方案4】:

我遇到了同样的问题。我解决无法访问合成实例变量的方法如下:

公共标头

@interface MyObject:NSObject {
}
@property (retain) id instanceVar;
@property (retain) id customizedVar;
@end

私有标头/实现

@interface MyObject()
@property (retain) id storedCustomizedVar;
@end

@implementation MyObject
@synthesize instanceVar, storedCustomizedVar;
@dynamic customizedVar;

- customizedVar {
  if(!self.storedCustomizedVar) {
    id newCustomizedVar;
    //... do something
    self.storedCustomizedVar= newCustomizedVar;
  }
  return self.storedCustomizedVar;
}

- (void) setCustomizedVar:aVar {
  self.storedCustomizedVar=aVar;
}

@end

它不是那么优雅,但至少它使我的公共头文件保持干净。

如果你使用KVO,你需要将customizedVar定义为storedCustomizedVar的依赖键。

【讨论】:

    【解决方案5】:

    我对 Obj-C 比较陌生(但对编程不熟悉),也对这个话题感到困惑。

    让我担心的方面是,无意中使用 iVar 而不是属性似乎相对容易。例如写作:

    myProp = someObject;

    而不是

    self.myProp = someObject;

    诚然,这是“用户”错误,但在某些代码中意外发生似乎仍然很容易,而且对于保留或原子属性,它可能会导致问题。

    理想情况下,我希望能够让运行时在生成 any iVar 时将某些模式应用于属性名称。例如。总是在它们前面加上“_”。

    目前我正在手动执行此操作 - 明确声明我的 ivars,并故意为它们赋予与属性不同的名称。我使用老式的“m”前缀,所以如果我的属性是“myProp”,我的 iVar 将是“mMyProp”。然后我使用@synthesize myProp = mMyProp 将两者关联起来。

    我承认这有点笨拙,而且有点额外的输入,但对我来说,能够在代码中更清楚地消除歧义似乎是值得的。当然,我仍然会弄错并键入 mMyProp = someObject,但我希望“m”前缀会提醒我我的错误。

    如果我可以只声明属性并让编译器/运行时完成其余的工作,那感觉会好很多,但是当我有很多代码时,我的直觉告诉我,如果我仍然必须这样做,我会以这种方式犯错遵循 init/dealloc 的手动规则。

    当然还有很多其他的事情我也会做错...

    【讨论】:

      猜你喜欢
      • 2012-03-28
      • 1970-01-01
      • 2019-02-01
      • 2016-08-19
      • 2011-10-03
      • 1970-01-01
      • 1970-01-01
      • 2022-09-23
      • 2014-09-21
      相关资源
      最近更新 更多