9

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

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

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

所以问题是:

  1. 我们应该在 init* 中使用属性访问器并在 Modern Runtime 中使用 dealloc 吗?

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

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

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

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

希望我的问题很清楚。


测试快速总结

  1. 如果您不在 legacy 中声明 ivar,编译器将完全不高兴

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

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

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

  5. @private当然,ivar 的声明允许直接访问 ivar,无论是传统的还是现代的

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

4

5 回答 5

10

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

你不能在当前的编译器中。未来的编译器应该解决这个问题。同时使用明确的@private ivars。@private ivar 不应被视为合同的一部分 - 这就是 @private 的含义,由编译器警告和链接器错误强制执行。

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

三个原因:(1)有一些重要的设计细节需要解决,(2)编译器工程师的时间有限,(3)@private ivars 通常足够好。

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

使用 OS X 10.6更新,64 位运行时确实允许通过self->ivar.

于 2009-06-17T16:59:42.727 回答
1

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

  • 我们应该在 init* 中使用属性访问器并在 Modern Runtime 中使用 dealloc 吗?

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

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

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

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

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

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

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

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

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

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

于 2009-06-17T15:55:00.690 回答
0

还有另一个具有类似信息的 SO question,但它并不完全重复。

底线来自Objective-C 2.0 文档,并引自Mark Bessey 的回答如下:

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

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

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

我的理解如下:

您不应该在init*dealloc方法中使用属性访问器,原因与您不应该在遗留运行时中使用它们的原因相同:如果您稍后覆盖属性方法并最终执行不应该做的事情,它会让您面临潜在的错误在init*or中完成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*anddealloc方法中访问合成的 ivar。我能想到的唯一问题是该@synthesize行可能必须位于源文件中您的和方法的定义之前。init*dealloc

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

于 2009-06-17T15:33:21.517 回答
0

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

公共标头

@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 的依赖键。

于 2009-07-26T15:26:41.350 回答
0

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

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

myProp = someObject;

代替

self.myProp = someObject;

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

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

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

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

如果我可以只声明属性并让编译器/运行时完成其余的工作会感觉更好,但是当我有很多代码时,我的直觉告诉我,如果我仍然必须遵循手动规则,我会这样犯错误用于初始化/释放。

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

于 2010-03-04T00:51:49.060 回答