为什么在某些情况下此 UIView 不添加为子视图,但在其他情况下却添加为子视图?
我有一个 UIView viewForRootVc
,它是 UIView 子类 NewView
的声明属性。 NewView
负责初始化 viewForRootVc
,但随后子类 NewViewSubClass
设置其背景颜色。完成这一切后,根视图控制器 RootVc
将 viewForRootVc
作为子视图添加到其视图中。
但这行不通。 viewForRootVc
实际上并未添加。
但是,如果我执行以下三件事中的任何一项(请记住我使用的是 ARC),它确实有效:
- 而不是在
NewViewSubClass
中设置viewForRootVc
的背景颜色,我在NewView
中设置了它。然后,我没有初始化NewViewSubClass
的实例,而是简单地初始化NewView
的实例。 - 在
NewView
中初始化viewForRootVc
时,我调用 setter(即 self.viewForRootVc),而不是仅仅使用直接赋值。 - 在
NewView
的头文件中将viewForRootVc
列为 ivar。
我不明白为什么这三件事中的任何一个都是必要的。
这是不起作用的代码: RootVc.m
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
NewView *newView = [[NewViewSubClass alloc] initWithFrame:CGRectMake(0, 0, 300, 300)];
//logs 0.00000, 0.00000
NSLog(@"%f, %f",newView.viewForRootVc.frame.size.width, newView.viewForRootVc.frame.size.height);
[self.view addSubview:newView.viewForRootVc];
//logs 0
NSLog(@"subviews: %i",[self.view.subviews count]);
}
NewView.h
@property (nonatomic, retain) UIView *viewForRootVc;
NewView.m
@synthesize viewForRootVc;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
viewForRootVc = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
}
return self;
}
NewViewSubClass.m
@synthesize viewForRootVc;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
viewForRootVc.backgroundColor = [UIColor greenColor];
}
return self;
}
我注意到的一件事是,如果我确实将 viewForRootVc
指定为 ivar,则不需要放置 @synthesize viewForRootVc;NewViewSubClass
的实现文件中的code>。
我一直理解声明的属性可以有效地实现以下目标:
- 允许其他对象访问它们。
- 允许在需要时调用二传手。
- 如果不使用 ARC,并且您在属性声明中指定了
retain
,则调用 setter 将自动保留该属性。 - 在 ARC 下,会自动为任何声明的属性生成 ivar。
但显然还有更多的事情要做。我不确定这是否是一个范围问题,或者我上面的非工作代码最终是否创建了两个不同版本的 viewForRootVc
——也许一个具有 NewView 中指定的正确框架
的 init
方法和具有 NewViewSubClass
的 init
方法中指定的正确背景颜色的方法,但两者都没有正确的框架和颜色。
我非常希望有人能为我一劳永逸地澄清声明属性与 ivars 的含义,以及调用 setter 来设置声明属性与直接为其赋值。
I have a UIView viewForRootVc
that is a declared property for a UIView subclass NewView
. It's NewView
's responsibility to initialize viewForRootVc
, but then a subclass NewViewSubClass
sets its background color. Once this is all done, the root view controller RootVc
adds viewForRootVc
as a subview to its view.
But this doesn't work. viewForRootVc
doesn't actually get added.
It DOES work, however, if I do any of the following three things (keep in mind that I am using ARC):
- Rather than setting the background color of
viewForRootVc
inNewViewSubClass
, I set it inNewView
. And then, rather than initializing an instance ofNewViewSubClass
, I simply initialize an instance ofNewView
. - When initializing
viewForRootVc
inNewView
, I call the setter (i.e. self.viewForRootVc) rather than just using direct assignment. - List
viewForRootVc
as an ivar in the header file ofNewView
.
I don't understand why any of these three things are necessary.
Here's the code that does NOT work:
RootVc.m
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
NewView *newView = [[NewViewSubClass alloc] initWithFrame:CGRectMake(0, 0, 300, 300)];
//logs 0.00000, 0.00000
NSLog(@"%f, %f",newView.viewForRootVc.frame.size.width, newView.viewForRootVc.frame.size.height);
[self.view addSubview:newView.viewForRootVc];
//logs 0
NSLog(@"subviews: %i",[self.view.subviews count]);
}
NewView.h
@property (nonatomic, retain) UIView *viewForRootVc;
NewView.m
@synthesize viewForRootVc;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
viewForRootVc = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
}
return self;
}
NewViewSubClass.m
@synthesize viewForRootVc;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
viewForRootVc.backgroundColor = [UIColor greenColor];
}
return self;
}
One thing I noticed is that if I do specify viewForRootVc
as an ivar, I don't need to put @synthesize viewForRootVc;
in the implementation file of NewViewSubClass
.
I always understood declared properties to effectively achieve the following:
- Allow other objects to access them.
- Allow a setter to be called when desired.
- If not using ARC, calling the setter will automatically retain the property if you specified
retain
in the property declaration. - Under ARC, an ivar is automatically generated for any declared properties.
But clearly there is more to it than this. I'm not sure if it's a scope issue, or if my non-working code above ends up creating two distinct versions of viewForRootVc
-- perhaps one that has the correct frame as specfied in NewView
's init
method and one that has the correct background color as specified in NewViewSubClass
's init
method, but neither has both the right frame and color.
I'm very much hoping someone can clarify once and for all for me the implications of declared properties vs. ivars, as well as calling the setter to set a declared property vs. assigning it a value directly.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
所以这里的问题是 NewViewSubClass.m 中的这一行:
@synthesize viewForRootVc;
编译器最终所做的是为子类实现一个新的 getter/setter 对。这与之前为超类中的同一属性实现的 getter/setter 对不同。
此外,这里和前面的 @synthesize 指令中还发生了一些事情,即编译器还生成一个 ivar 来支持此属性。但是,创建的 ivar 仅在每个特定实现中可见。换句话说,两个 @synthesize 指令最终都会创建自己独特的 ivar。
然后我们在 NewViewSubClass.m 中找到这段代码:
这里
viewForRootVc
是来自子类的 ivar。此时为零。事实上,在你的情况下它总是为零并且永远不会被设置。因为如果您查看 NewView.m 中的这段代码:超类正在设置名为“viewForRootVc”的其 ivar,而不是在子类中生成的同名 ivar。
我希望这是有道理的。因此,让我们看一下“修复”此问题的三种方法,以准确了解每种情况下发生的情况:
在 NewView 中设置背景 ->这基本上使用了正确的ivar(来自NewView的ivar),这有效地避免了我们一直在讨论的问题。
从 NewView 调用 setter ->这是有趣的一点。通过调用 setter,您实际上是在调用在子类中实现的 setter。这反过来又在子类中使用 ivar,因此一切都“有效”(尽管我认为这并不理想。)
显式声明 ivar ->这以不同的方式避免了这个问题。通过声明 ivar,可以防止编译器从 @synthesize 指令生成自己的 ivar。并且因为默认情况下 ivar 是 @protected 的,所以 ivar 在子类中也是可见的,因此两组 setter/getter 最终都使用相同的 ivar。
IMO,正确的修复方法是让 ivars 对于类而言是私有的(@synthesized ivars 是隐藏的,因此实际上是私有的),并让子类根据需要引用属性 getter/setter。当然,不要在 NewViewSubclass.m 中再次@synthesize 该属性,
我希望这会有所帮助。
So the issue here is this line in NewViewSubClass.m:
@synthesize viewForRootVc;
What the compiler ends up doing there is implementing a new getter/setter pair for the subclass. This is distinct from the previous getter/setter pair that were implemented for the same property in the superclass.
In addition, something else happens here and in the previous @synthesize directive, which is that the compiler also generates an ivar to back this property. However, this ivar that is created is visible only within each particular implementation. So in other words, each of the two @synthesize directives ends up creating its own distinct ivar.
So then we get to this bit of code here in NewViewSubClass.m:
Here
viewForRootVc
is the ivar from the subclass. It is nil at this point. In fact, in your case it is always nil and is never set. Because if you look at this code from NewView.m:The superclass is setting its ivar called 'viewForRootVc', not the ivar with the same name generated in the subclass.
I hope that makes sense. So let's look at the three ways in which you "fixed" this to understand exactly what happened in each case:
Set the background in NewView -> This basically uses the correct ivar (the one from NewView), which effectively avoids the problem we've been talking about.
Calling the setter from NewView -> This is the interesting one. By calling the setter, you are actually calling the setter implemented in the subclass. This in turn, uses the ivar in the subclass, and so everything "works" (although, I think this is not ideal.)
Explicitly declaring the ivar -> This avoids the issue in a different way. By declaring the ivar, you prevent the compiler from generating its own ivar from the @synthesize directive. And because ivars are, by default, @protected, that ivar is also visible in the subclass, so both sets of setters/getters end up using the same ivar.
The right fix, IMO, is let ivars be private to a class (@synthesized ivars are hidden and therefore effectively private), and have subclasses reference the property getter/setter as needed. And then of course don't @synthesize the property again in your NewViewSubclass.m
I hope that helps.