我有一个自定义视图,希望它能够初始化in-code
和nib
。
写initWithFrame
和initWithCoder
方法的正确方法是什么?它们都共享一个用于某些初始化的代码块。
在这种情况下,正确的做法是创建另一个包含-initWithFrame:
和-initWithCoder:
公用代码的方法,然后从-initWithFrame:
和-initWithCoder:
调用该方法:
- (void)commonInit
{
// do any initialization that's common to both -initWithFrame:
// and -initWithCoder: in this method
}
- (id)initWithFrame:(CGRect)aRect
{
if ((self = [super initWithFrame:aRect])) {
[self commonInit];
}
return self;
}
- (id)initWithCoder:(NSCoder*)coder
{
if ((self = [super initWithCoder:coder])) {
[self commonInit];
}
return self;
}
请注意Justin的回答中列出的问题,特别是任何子类都不能覆盖-commonInit
。我在这里使用这个名称是为了说明它的价值,但您可能想要一个与您的类联系更紧密、不太可能被意外重写的名称。如果您正在创建一个专门构建的UIView子类,而该子类本身不太可能被子类化,那么使用如上所述的通用初始化方法是完全可以的。如果你正在编写一个供他人使用的框架,或者如果你不理解这个问题,但想做尽可能安全的事情,请使用静态函数。
解决方案并不像最初看起来那么简单。初始化过程中存在一些危险,下面将详细介绍。出于这些原因,我通常在objc程序中采用以下两种方法之一:
对于琐碎的情况,复制是一个不错的策略:
- (id)initOne
{
self = [super init];
if (nil != self) { monIntIvar = SomeDefaultValue; }
return self;
}
- (id)initTwo
{
self = [super init];
if (nil != self) { monIntIvar = SomeDefaultValue; }
return self;
}
对于不常见的情况,我推荐一个静态初始化函数,它采用一般形式:
// MONView.h
@interface MONView : UIView
{
MONIvar * ivar;
}
@end
// MONView.m
static inline bool InitMONView(MONIvar** ivar) {
*ivar = [MONIvar new];
return nil != *ivar;
}
@implementation MONView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (nil != self) {
if (!InitMONView(&ivar)) {
[self release];
return nil;
}
}
return self;
}
- (id)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (nil != self) {
if (!InitMONView(&ivar)) {
[self release];
return nil;
}
}
return self;
}
// …
@end
Objective-C++:
如果您使用的是objc++,那么您可以简单地为您的c++ivar实现合适的默认构造函数,并省略大部分初始化和解除锁定脚手架(假设您正确地启用了编译器标志)。
更新:
我将解释为什么这是初始化对象的安全方法,并概述为什么其他答案的典型实现是危险的一些原因。
在初始化期间调用实例方法的常见初始化程序的典型问题是它们滥用继承图,通常会引入复杂性和错误。
建议:在部分构造的对象上调用重写的实例方法(例如,在初始化和解除锁定期间)是不安全的,需要避免。访问者尤其糟糕。在其他语言中,这是程序员的错误(例如UB)。查看主题的objc文档(参考:"Implementing an Initializer")。我认为这是必须的,但我仍然认识一些人,他们坚持认为实例方法和访问器在部分构建的状态下更好,因为它">通常适用于他们"。
要求:尊重继承图。从基础开始初始化。自上而下的破坏。总是
建议:保持所有初始化的一致性。如果你的base从init返回了一些东西,你应该假设一切都很好。不要为要实现的客户端和子类引入脆弱的初始化舞蹈(它很可能会以bug的形式出现)。你需要知道你是否持有一个有效的实例。此外,当您从指定的初始化器返回对象时,子类将(正确地)假设您的基已正确初始化。您可以通过将基类的ivar设为私有来降低这种情况发生的概率。从init返回后,客户端/子类会认为它们派生的对象是可用的,并且初始化正确。随着类图的增长,情况变得非常复杂,错误开始慢慢出现。
建议:检查init中的错误。同时保持错误处理和检测的一致性。返回nil是确定初始化期间是否出现错误的明显约定。及早发现。
好吧,但是共享实例方法呢?
exmaple从另一个岗位借用和修改:
@implementation MONDragon
- (void)commonInit
{
ivar = [MONIvar new];
}
- (id)initWithFrame:(CGRect)aRect
{
if ((self = [super initWithFrame:aRect])) {
[self commonInit];
}
return self;
}
- (id)initWithCoder:(NSCoder*)coder
{
if ((self = [super initWithCoder:coder])) {
[self commonInit];
}
return self;
}
// …
(顺便说一句,在那个例子中没有错误处理)
Caleb:我在上面的代码中看到的最大的"危险"是,有人可能会创建有问题的类的子类,覆盖-commonInit,并可能初始化对象两次。
特别是,子类-[MONDragon commonInit]将被调用两次(由于将创建两次资源而泄漏资源),并且基的初始化器和错误处理将不会执行。
Caleb:如果这是一个真正的风险…
任何一种效果都可能等同于一个不可靠的程序。通过使用常规初始化可以容易地避免该问题。
Caleb:…处理它的最简单方法是保持-commonInit私有化和/或将其记录为不覆盖的东西
由于运行时在消息传递时不区分可见性,这种方法很危险,因为任何子类都可以很容易地声明相同的私有初始化方法(见下文)。
将一个方法记录为不应该覆盖的东西会暴露子类的负担,并引入复杂性和问题,这些问题可以通过使用其他方法轻松避免。它也很容易出错,因为编译器不会标记它
如果坚持使用实例方法,那么在大多数情况下,保留-[MONDragon constructMONDragon]
和-[MONKomodo constructMONKomodo]
等约定可以显著减少错误。初始化器可能只对类实现的TU可见,因此编译器可以标记一些潜在的错误。
附带说明:一个常见的对象构造函数,例如:
- (void)commonInit
{
[super commonInit];
// init this instance here
}
(我也看到过)更糟糕,因为它限制了初始化,删除了上下文(例如参数),而且最终仍然会有人在指定的初始化器和-commonInit
之间的类中混合初始化代码。
通过所有这些,由于一般的误解和愚蠢的错误/疏忽,我浪费了大量的时间来调试所有上述问题,我得出结论,当您需要实现类的公共初始化时,静态函数是最容易理解和维护的。类应该将它们的clant与危险隔离开来,这是"通过实例方法的通用初始化器"多次失败的问题。
它不是基于指定方法的OP中的一个选项,但需要注意的是:通常可以使用方便的构造函数更容易地合并公共初始化。在处理类集群、可能返回专门化的类以及可能选择从多个内部初始化程序中进行选择的实现时,这对于最小化复杂性特别有用。
如果它们共享代码,只需让它们调用第三个初始化方法。
例如,initWithFrame
可能看起来像这样:
- (id)initWithFrame:(CGRect)frame {
if ((self = [super initWithFrame:frame])) {
[self doMyInitStuff];
}
return self;
}
请注意,如果您使用的是OS X(而不是iOS),则帧将为NSRect
,而不是CGRect
。
如果你需要进行错误检查,让你的初始化方法返回一个错误状态,如下所示:
- (id)initWithFrame:(CGRect)frame {
if ((self = [super initWithFrame:frame])) {
if (![self doMyInitStuff]) {
[self release];
self = nil;
}
}
return self;
}
这假设doMyInitStuff
方法在出现错误时返回NO
。
此外,如果您还没有看过,有一些关于初始化的文档可能对您有用(尽管它没有直接解决这个问题):
Cocoa的编码指南:框架开发人员的提示和技术