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

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

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

所以问题是:

  1. 我们应该在 init* 和 dealloc 中使用现代运行时的属性访问器吗?

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

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

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

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

希望我的问题是清楚的。


测试快速总结:

  1. 如果你没有在legacy中声明ivar,编译器会完全不高兴

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

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

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

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

现在并没有真正提供一个干净的前进方式,不是吗?

其他提示

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

  • 我们应该在 init* 和 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。

另一个具有类似信息的SO问题,但这并不是一个重复。

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

  

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

     

对于遗留运行时,必须已在@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仍然有效,这仍然是你最安全的选择。

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

公共标题

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

我对Obj-C比较陌生(但不是编程),也被这个话题搞糊涂了。

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

myProp = someObject;

而不是

self.myProp = someObject;

不可否认,这是“用户”。错误,但在某些代码中看起来仍然很容易意外,对于保留或原子属性,它可能会导致问题。

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

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

我承认这有点笨拙,还有一些额外的打字,但对我来说,能够在代码中更明确地消除歧义似乎是值得的。当然我仍然可以解决它并键入mMyProp = someObject,但我希望'm'前缀会提醒我我的错误。

如果我能够声明属性并让编译器/运行时完成剩下的工作会感觉好多了,但是当我有很多代码时,我的直觉告诉我,如果我仍然需要,我会犯这样的错误遵循init / dealloc的手动规则。

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

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top