我对属性重新声明有疑问
概述:
- 类"A"是具有只读属性int n1的父类
- 类"B"是子类,它将属性重新声明为读写
- 使用类"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 = %in", _n1); //I expected _n1 and self.n1 to display the same value
printf("self.n1 = %inn", 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
测试.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
有两种方法。在这两种情况下,都不在子类中调用@synthesize
我很惊讶能为你编译。我预计会出现类似"属性'n1'试图使用在超类'A'中声明的ivar'_n1'"的错误无论如何,这绝对不是你真正能做的事情,这就是为什么你会看到奇怪的行为。(我记得为什么你没有看到这个错误;这是因为单独的编译单元。你只是得到了不同的ivar。(
首先,您需要了解@dyanmic
。这是一种告诉编译器"是的,我知道你在这里没有看到所需方法的实现;我保证它会在运行时出现。"在子类中,你将使用@dynamic
让编译器知道可以继承n1
。
@implementation B
@dynamic n1;
@end
现在,您需要提供setN1:
方法。IMO,子类不应该干扰其超类的ivar,所以我赞成合成的ivar标记为@private
的事实。稍后,我将告诉您如何撤消它,但现在让我们来处理我喜欢的解决方案:
- 在
A
中将setN1:
实现为私有方法 - 将其暴露在
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
现在,有些人认为子类可以扰乱其超类的ivar。这些人错了(好吧,IMHO…(,但在ObjC中这是可能的。您只需要声明ivar @protected
即可。当您直接在@interface
中声明ivar时,这是默认的(这是您不应该再这样做的众多原因之一(。它看起来是这样的:
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
我复制了您的代码并验证了您的行为。我可以解释它的机制,但不能解释它背后的逻辑
发生的情况如下:两个@synthesize
指令中的每一个都在其相应的类中生成一个隐藏变量_n
。此外,该指令合成了A
中n1
的getter和B
中的getter/setter对。B
中n1
的getter覆盖A
中n1
的getter;setter没有,因为没有什么可重写的。
此时,B
中A
的_n1
变为孤立:n1
的getter和它的setter都没有引用它。setter引用B
的_n1
,而不是A
的。这就是为什么在A
的display
方法中打印出不同的值。将该方法放入B
中的行为与您所期望的一样。
编辑:
自然,下一个问题是如何做出你想要的行为。结果很简单:不合成B
中的属性,而是在A
的实现文件中实现_n1
的setter(不将其放在接口中,这样它对接口的客户端保持只读(。
// This goes in A.m without a declaration in A.h
- (void) setN1:(int)n1 {
_n1 = n1;
}