3

我有一个 UIView viewForRootVc,它是 UIView 子类的声明属性NewView。初始化是NewView的责任viewForRootVc,但随后子类NewViewSubClass设置其背景颜色。完成这一切后,根视图控制器RootVcviewForRootVc作为子视图添加到其视图中。

但这不起作用。viewForRootVc实际上并没有被添加。

但是,如果我执行以下三件事中的任何一项(请记住我使用的是 ARC),它确实有效:

  1. 我没有设置viewForRootVcin的背景颜色,而是将其设置为。然后,我不初始化 的实例,而是简单地初始化 的实例。NewViewSubClassNewViewNewViewSubClassNewView
  2. 在初始化viewForRootVcNewView,我调用setter(即self.viewForRootVc),而不是仅仅使用直接赋值。
  3. viewForRootVc在头文件中作为 ivar列出NewView

我不明白为什么这三件事都是必要的。

这是不起作用的代码: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]);
}

新视图.h

@property (nonatomic, retain) UIView *viewForRootVc;

新视图.m

@synthesize viewForRootVc;

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        viewForRootVc = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
    }
    return self;
}

新视图子类.m

@synthesize viewForRootVc;

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        viewForRootVc.backgroundColor = [UIColor greenColor];
    }
    return self;
}

我注意到的一件事是,如果我指定为viewForRootVcivar,我不需要放入.@synthesize viewForRootVc;NewViewSubClass

我一直理解声明的属性可以有效地实现以下目标:

  1. 允许其他对象访问它们。
  2. 允许在需要时调用 setter。
  3. 如果不使用 ARC,如果您retain在属性声明中指定,调用 setter 将自动保留该属性。
  4. 在 ARC 下,任何声明的属性都会自动生成一个 ivar。

但显然,它的意义远不止于此。我不确定这是否是范围问题,或者我上面的非工作代码是否最终创建了两个不同的版本——也许一个具有's方法viewForRootVc中指定的正确框架,另一个具有正确的背景颜色在's方法中指定,但两者都没有正确的框架和颜色。NewViewinitNewViewSubClassinit

我非常希望有人可以一劳永逸地为我澄清声明的属性与 ivars 的含义,以及调用 setter 来设置声明的属性与直接为其赋值。

4

1 回答 1

3

所以这里的问题是 NewViewSubClass.m 中的这一行:

@synthesize viewForRootVc;

编译器最终会为子类实现一个新的 getter/setter 对。这与之前为超类中的相同属性实现的 getter/setter 对不同。

此外,这里和前面的 @synthesize 指令中还发生了其他事情,即编译器还生成了一个 ivar 来支持该属性。但是,创建的这个 ivar 仅在每个特定实现中可见。所以换句话说,两个 @synthesize 指令中的每一个最终都会创建自己独特的 ivar

那么我们在 NewViewSubClass.m 中得到这段代码:

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {

        // this ivar is the one from the subclass. It is still nil at this point!
        viewForRootVc.backgroundColor = [UIColor greenColor];
    }
    return self;
}

viewForRootVc是来自子类的 ivar。在这一点上为零。事实上,在你的情况下,它总是 nil 并且永远不会设置。因为如果你从 NewView.m 看这段代码:

@synthesize viewForRootVc;

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {

        // Here viewForRootVc is the ivar generated for this class
        // this is not the same as the ivar with the same name in our subclass

        viewForRootVc = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];
    }
    return self;
}

超类将其 ivar设置为“viewForRootVc”,而不是在子类中生成的同名 ivar。

我希望这是有道理的。因此,让我们看一下您“修复”此问题的三种方式,以准确了解每种情况下发生的情况:

  1. 在 NewView 中设置背景 -> 这基本上使用了正确的 ivar(来自 NewView 的那个),这有效地避免了我们一直在谈论的问题。

  2. 从 NewView 调用 setter -> 这是有趣的。通过调用 setter,您实际上是在调用子类中实现的 setter 。这反过来又在子类中使用了 ivar,所以一切都“有效”(尽管我认为这并不理想。)

  3. 显式声明 ivar -> 这以不同的方式避免了该问题。通过声明 ivar,可以防止编译器从 @synthesize 指令生成自己的 ivar。并且因为 ivar 默认情况下是@protected,所以 ivar 在子类中也是可见的,所以两组 setter/getter 最终都使用相同的 ivar。

正确的解决方法是,IMO,让 ivars 对某个类是私有的(@synthesized ivars 是隐藏的,因此实际上是私有的),并让子类根据需要引用属性 getter/setter。然后当然不要在你的 NewViewSubclass.m 中再次@synthesize 属性

我希望这会有所帮助。

于 2012-01-09T19:05:59.080 回答