【问题标题】:What is the best way to ensure that a property is present during initialization?确保在初始化期间存在属性的最佳方法是什么?
【发布时间】:2011-08-31 15:34:59
【问题描述】:

我创建了一个类Metrics,它被设计为子类以自定义行为。为了使其更加健壮,Metrics 的 init 方法调用了一个名为 setup 的方法,该方法默认不执行任何操作。如果子类想要在初始化期间自定义行为(他们通常会这样做),他们可以重写此方法。由于默认实现什么都不做,因此无需记住调用[super setup]

到目前为止,我喜欢它的工作方式,它强大且易于使用。我现在遇到的问题是有时设置方法需要设置一些额外的属性。一个例子:

@implimentation Metrics

- (id)initWithFrame:(CGRect)frame {
  self = [super initWithFrame:frame];
  if (self) {
    // Do all the required initialization
  }
}

@end

@interface SubclassOfMetrics : Metrics {}

@property (assign) CGFloat width;

@end

@implimentation SubclassOfMetrics

- (void)setup {
  // This method is indirectly called as a result of the superclass initialization
  // The code here depends on the `width` property being set
}

@end

我遇到的问题是 setup 在属性 width 可以设置之前被调用。我在这里有什么选择?我可以通过不在 init 方法中初始化我的 Metrics 类来解决这个问题。在我设置了我需要设置的属性之后,我可以明确地做到这一点。我不喜欢这个,因为它要求我按特定顺序执行操作并记住设置。我还有其他选择吗?

编辑:问题的根源实际上是Metrics 类的初始化做了很多计算。这些计算的结果将取决于子类中设置的属性,因此我需要一种方法来设置这些属性超类完成初始化之前。

【问题讨论】:

  • +1... 有趣的问题。我确定这是有原因的,但是在这种情况下设置的目的是什么?子类不能在自己的init方法而不是setup方法中实现它们的初始化行为吗?
  • 覆盖初始化器是可能的,但由于类的实例化方式而更加困难。进行初始化的类不知道SubclassOfMetrics,因此它不知道调用特殊的init 方法。这是类集群。
  • 另外,拥有setup 方法使子类化变得非常容易,而且这个类被设计为子类化,所以我想让它尽可能简单和健壮。
  • 另外,setup 方法不是直接从 init 方法调用的(就像在我的示例中一样),它是在代码通过几个其他类之后在堆栈中向下调用大约 5 帧除了Metrics
  • @kubi 如果实例化 SubclassOfMetrics 的类对此一无所知,为什么该子类的新属性很重要。听起来您的设置将分配默认值。您可能想看看如何为您的“类集群”实现class cluster

标签: objective-c initialization


【解决方案1】:

对象的初始化程序已经设计为您尝试创建的这种“设置”。在调用超级初始化程序后,您应该只使用初始化程序并执行特定于类的任何必要设置。当您有需要在初始化(设置)时考虑的新属性时,为您的子类创建一个新的指定初始化程序。这种依赖注入将改善您的整体设计并满足您的初始化需求。

@implementation Metrics

- (id)initWithFrame:(CGRect)frame {
  self = [super initWithFrame:frame];
  if (self) {
    //Just setup here, or call a method if you prefer
  }
}

@end

@interface SubclassOfMetrics : Metrics {}

@property (assign) CGFloat width;

- (id)initWithFrame:(CGRect)frame andMetricWidth:(CGFloat)inWidth;

@end

@implementation SubclassOfMetrics

@synthesize width;

- (id)initWithFrame:(CGRect)frame andMetricWidth:(CGFloat)inWidth {
  self = [super initWithFrame:frame];
  if (self) {
    self.width = inWidth;
    //do your setup here and use the width
  }
}

@end

【讨论】:

  • 我更新了我的问题。部分问题,可能是整个问题的根源,就是超类的初始化依赖于子类的属性,所以我需要确保在初始化之前就设置好那些属性。
  • 最简单的解决方案是明确告诉Metrics 类在您完成属性设置后自行设置……但这对我来说很有趣,我正在寻找更好的解决方案。
【解决方案2】:

如果您的Metrics 对象是另一个对象的实例变量,例如控制器或任何其他类,则一旦确定了对象设置所需的信息,您就可以使用该类向Metrics 发送消息.我经常会做这样的事情:

Metrics*myMetrics=[[Metrics alloc] init]; // just creates the Metrics object

我可以在控制器的viewDidLoad 或任何合适的地方执行此操作。

然后,您可以从您的控制对象执行此操作:

[self.myMetrics setup];

如果它需要像“宽度”这样的东西,你可以设置一个Metrics ivar 并从setup 中调用它,或者将宽度或任何你需要的东西作为参数发送给setup 方法。这里有很多不同的方法。 (或者如果width 已经是Metrics 的属性,就像frame 属性一样,那么您不需要传递它。只需在设置Metrics 帧大小后调用setup,如果适用.)

【讨论】:

  • 这会起作用,但我不喜欢必须调用设置方法的想法。如果你忘记了,你会得到一大堆未初始化的对象。
  • 是的,这仅取决于您的整体架构和风格。我个人经常使用这种方法,但我也在我对视图和对象的动态操作中虔诚地使用通知中心,所以很多都是自动处理的。
【解决方案3】:

我不明白这个问题。 Setup 通常不应依赖子类的行为。如果设置依赖于self.width,则覆盖子类中的width getter。那应该给你子类的width

看看下面的简单设置。方法- (CGFloat) width 覆盖Metrics 中的合成getter 并很好地返回17.33

#import <Foundation/Foundation.h>

@interface Metrics : NSObject 
- (id) init;
- (void) setup;
@property (assign, nonatomic) CGFloat width;
@end

@interface SubclassOfMetrics : Metrics
- (CGFloat) width;
@end

@implementation Metrics
@synthesize width;
- (id) init
{
    self = [super init];
    if (self)
        [self setup];

    return self;
}
- (void) setup;
{
    NSLog(@"%f", self.width);
}
@end

@implementation SubclassOfMetrics
- (CGFloat) width
{
    return 17.33;
}
@end


int main (int argc, const char * argv[])
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    Metrics *met = [[SubclassOfMetrics alloc] init];
    [met release];

    [pool drain];
    return 0;
}

输出如预期:

2011-08-31 18:09:41.630 MetricsTest[44098:707] 17.330000

这假设在调用setup 时已知width(例如,作为参数传递给SubclassOfMetricsinitXYZ: 方法)。否则,一旦设置了实际的 width,您就必须手动调用 setup

【讨论】:

  • 我的例子是一个必要的简化。更详细的描述是 Metrics 设置一个视图,将自己设置为委托,SubclassOfMetrics 实现委托方法。 Metrics 没有直接调用子类方法。除了我们的情况比那更复杂。
  • 但它可以“虚拟调用”子类方法。决定何时知道宽度。
【解决方案4】:

我在初始化超类之前设置了 iVar。

- (id)initWithFrame:(CGRect)frame andMetricWidth:(CGFloat)inWidth {
  _width = inWidth

  self = [super initWithFrame:frame];
  if (self) {
  }

  return self;
}

我有意直接设置 iVar 以避免使用属性语法产生任何潜在的副作用。我还没有完全考虑过这将如何处理 ARC 下的对象,但它可以很好地处理原语。

【讨论】:

    【解决方案5】:

    我不确定是否有特殊原因不能将 -setup 中的功能简单地重构为 -initWithFrame:,因此我将拒绝此答案,并要求查看更准确的版本你的方法,所以我们可以看到 -setup 何时被调用。

    事实上,如果你想要一个正确初始化的对象——尤其是当它涉及到类层次结构时,你必须记住在某个时间点、某个地方“按特定顺序执行操作”。您当前示例的工作方式是,Metrics 的所有子类以及它们的子类都调用相同的大多数无用的 init 方法。这可能很容易放入第一代子类并自动调用其版本的-setup,因为它覆盖了其父级的无操作方法,如果有一天你认为它值得子类subclassOfMetrics,你会去的最终替换-setup 的功能版本,或者必须记住调用[super setup] 来获取功能,无论如何都违背了你的破解目的。

    这当然是假设您不必担心添加新的 ivars,您当然会这样做。

    事实上,-init 函数专门用于初始化类的 ivars,并且设置了通用约定,以便您在每个类中只有一个函数,您必须记住“执行操作”按一定的顺序”。一系列编写良好的 init 函数让您不必担心何时调用像 -setup 这样的方法更深入函数堆栈 5 帧。当前存在“调用 super 的 init,初始化我的 ivars,进行无关设置”的约定,因为它确保类的所有设置都以正确的顺序完成,无论您的层次结构最终有多少代。

    代码是样板文件(虽然仍然保持很小),您需要做出的唯一真正决定是“是否应该在init 中进行这部分必要的设置,它将影响所有孩子,或者应该它包含在一个 -setup 函数中,该函数只会被调用一次,并且会非常特定于我的班级?” (加粗表示重要性)​​

    然后,一旦你有了一个好的 init,你就可以创建一个简单的类方法,如下所示:

    + (Metric*) newMetricWithFrame:(CGRect)frame {
        Metrics* theMetric = [[self alloc] init]; //this will allow you to call it on whatever class you want and get the correct, fully initialized object
        [theMetric setup]; //this will call the correct setup method
        return theMetric;
    }
    

    然后您可以像这样[subclassOfMetrics newMetricWithFrame:frame]; 调用该函数,您将返回一个对象,编译器会将其视为Metric,但它将以subclassOfMetrics 响应所有方法调用。您还可以强制转换返回结果,以便编译器不会抱怨在其上调用特定于子类的方法:

    subclassOfMetrics* theSub = (subclassOfMetrics*) [subclassOfMetrics newMetricWithFrame:frame]; [theSub subclassSpecificMethod];

    【讨论】:

      【解决方案6】:

      您可以让您的控制器在调用 setup 之前调用 Metrics 子类中的方法 - 或者,如果您不喜欢这种方法,请使用 notification center 并注册 Metrics在您的子类中完成计算时收到通知,然后在您的子类中在他们完成计算时发布通知。做这种事情是非常简单和方便的方法。当Metrics 收到此通知时,它会运行setup 方法。如果Metrics 是一个视图或您希望在显示到屏幕之前完全设置的东西,请添加另一个notification,当Metrics 使用setup 完成时,视图控制器会收到该setup,然后让控制器addSubview 一切都完成之后。

      【讨论】:

      • 通知中心解决方案没有帮助。我仍然必须发布通知。为什么不直接调用超类?
      • 调用超类的程序设计真的很糟糕。发布通知有什么问题?通知中心是为这样的事情(以及更多)而设计的。如果 A 依赖于 B 而 B 依赖于 A,为什么不使用非常简单的通知中心来传递消息,以便对象根据其他参数知道何时做某事?
      • 这是完全相同的问题。子类需要发布通知或调用超类方法。无论哪种方式,您在两者之间仍然存在一定程度的依赖关系。
      • nsnotification center 是一种向超类发送信息比直接调用超类更加优越和安全的方法
      • 怎么样?调用起来更复杂,它需要更改超类,它是异步的(在这种情况下我不需要或不想要)。我只看到缺点,没有优点。而且,FWIW,回调超类在 Apple 自己的类中非常很常见。见[self setNeedsDisplay];
      猜你喜欢
      • 2011-05-10
      • 2010-09-17
      • 1970-01-01
      • 1970-01-01
      • 2017-11-01
      • 2012-01-28
      • 2011-04-23
      • 2010-10-13
      相关资源
      最近更新 更多