在初始化期间确保属性存在的最佳方法是什么?

时间:2011-08-31 15:34:59

标签: objective-c initialization

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

我喜欢它迄今为止的工作方式,它坚固且易于使用。我现在遇到的问题是有时候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

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

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

6 个答案:

答案 0 :(得分:2)

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

@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

答案 1 :(得分:1)

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

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

我可能会在控制器的viewDidLoad或适当的地方执行此操作。

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

[self.myMetrics setup];

如果它需要类似“宽度”的内容,您可以设置Metrics ivar并从setup内调用它,或者将宽度或其他任何您需要的参数作为参数发送到{{1} } 方法。在这里有很多不同的方法。 (或者如果setup已经是width的属性,就像Metrics属性一样,那么您就不需要传递它。只需在设置完成后调用frame setup框架尺寸(如果适用)。)

答案 2 :(得分:0)

我不明白这个问题。 Setup通常不应该依赖于子类的行为。如果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

这假定在width被调用时initXYZ:已知(例如,作为SubclassOfMetrics setup方法的参数传递)。否则,只要设置了实际的setup,您就必须手动拨打width

答案 3 :(得分:0)

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

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

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

  return self;
}

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

答案 4 :(得分:0)

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

事实是,在某个地方,某个地方,如果你想要一个正确初始化的对象,你必须记住“以某种顺序执行动作” - 特别是当它涉及类层次结构时。当前示例的工作方式,Metrics的所有子类及其子类都调用相同的无用的init方法。这可以很容易地放入第一代子类并自动调用它的-setup版本,因为如果你有一天认为它值得sublcass subclassOfMetrics,那么它会覆盖其父级的无操作方法,你将最终取代-setup的功能版本,或者不得不记得打电话给[super setup]来获取功能,无论如何都要打败你的黑客目的。

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

事实上,-init函数专门用于初始化类的ivars,并且设置了常规约定,以便在每个类中只有一个函数,您必须记住“按特定顺序执行操作“。一系列精心编写的初始化函数使您不必担心何时调用类似-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的对象,但它将作为{{1}响应所有方法调用}}。您还可以转换返回结果,以便编译器不会抱怨在其上调用特定于子类的方法:

subclassOfMetrics

答案 5 :(得分:0)

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