财产声明 - ivar和getter值不匹配

时间:2011-12-07 03:21:21

标签: objective-c inheritance properties redeclaration

我对财产重新申报表示怀疑

概述:

  • class“A”是具有readonly属性int n1;
  • 的父类
  • class“B”是子类,它将该属性重新声明为read write
  • 使用类“B”的setter将属性值设置为20
  • 当我使用getter和实例变量打印值时,我似乎得到了不同的值

注意事项: - 内存管理= ARC(自动引用计数)

问题:

  • 当我打印self.n1和_n1的值时,为什么我会得到不同的值?
  • 我的预期行为和实际行为与原因不符(请向下滚动以查看实际与预期的对比)?

代码:(在单独的文件中)

A.H

#import<Foundation/Foundation.h>

@interface A : NSObject

@property (readonly) int n1;

- (void) display;

@end

A.M

#import "A.h"

@implementation A

@synthesize n1 = _n1;

- (void) display
{
    printf("_n1     = %i\n", _n1);                  //I expected _n1 and self.n1 to display the same value
    printf("self.n1 = %i\n\n", self.n1);            //but they seem to display different values
}

@end

B.h

#import"A.h"

@interface B : A

@property (readwrite) int n1;

@end

B.m

#import"B.h"

@implementation B

@synthesize n1 = _n1;

@end

test.m

#import"B.h"

int main()
{
    system("clear");

    B* b1 = [[B alloc] init];

    b1.n1 = 20;

    [b1 display];   //Doubt - my expected behavior is different from actual behavior


    return(0);
}

预期行为:

_n1     = 20
self.n1 = 20

实际行为:

_n1     = 0
self.n1 = 20

2 个答案:

答案 0 :(得分:7)

有两种方法可以解决这个问题。在这两种情况下,您都不会在子类中调用@synthesize我很惊讶为你编译。我希望像“属性'n1'这样的错误尝试使用在超类'A'中声明的ivar'_n1'。无论如何它肯定不是你真正可以做的事情,这就是你看到奇怪的原因行为。 (我记得为什么你没有看到这个错误;这是因为单独的编译单元。你只是用不同的ivars结束。)

首先,您需要了解@dyanmic。这是一种告诉编译器的方法“是的,我知道你没有在这里看到所需方法的实现;我保证它会在运行时存在。”在子类中,您将使用@dynamic让编译器知道可以继承n1

@implementation B
@dynamic n1;
@end

现在,您需要提供setN1:方法。 IMO,子类不应该搞乱他们的超类的ivars,所以我赞同合成的ivars标记为@private的事实。在一瞬间,我会告诉你如何解除这个问题,但现在让我们来处理我的首选解决方案:

  • setN1:中将A实施为私有方法。
  • 将其展示在B

A.H

@interface A : NSObject
@property (readonly) int n1;
- (void) display;
@end

A.M

#import "A.h"

@interface A () // Private class extension, causes setN1: to be created but not exposed.
@property (readwrite) int n1;
@end
@implementation A

@synthesize n1 = _n1;

- (void) display {
   ...
}
@end

B.h

#import "A.h"    
@interface B : A
@property (readwrite) int n1; // Tell the world about setN1:
@end

B.m

#import "B.h"
@implementation B
@dynamic n1; // Yes compiler, setN1: exists. I promise.
@end

现在,有些人认为子类可以搞乱他们的超类的ivars。那些人是错的(好吧,恕我直言......)但是在ObjC中是可能的。你只需要声明ivar @protected。当您直接在@interface中声明ivars时,这是默认设置(这是您不应再这样做的众多原因之一)。它看起来像这样:

A.H

@interface A : NSObject {
  int _n1;
}
...

A.m - 删除使超类中的n1可写的额外类扩展。

B.h - 没有变化

B.m

@implementation B
@dynamic n1;

- (void)setN1:(int)n1 {
  _n1 = n1;
}
@end

答案 1 :(得分:2)

我复制了您的代码并验证了您获得的行为。我可以解释它的机制,但不能解释它背后的逻辑。

以下是正在发生的事情:两个@synthesize指令中的每一个都在其对应的类中生成隐藏变量_n。此外,该指令在n1中合成了A的getter,在B中合成了一个getter / setter对。 n1B的吸气者会覆盖n1A的吸气者;设定者没有,因为没有什么可以覆盖。

此时,A中的_n1 B成为孤儿:n1的吸气者及其设定者都没有参考它。 setter引用B的{​​{1}},而不是_n1。这就是为什么您会看到A display方法中打印的不同值。将方法放在A中的行为与您期望的一样。

编辑:

当然,接下来的问题是如何制作你想要的行为。结果很简单:不要在B中合成属性,并在B的实现文件中实现_n1的setter(不将其放在接口中,以便它对您的界面的客户端保持只读。)

A