I have a custom view that I want to be able to initialize both in-code
and in nib
.
我有一个自定义视图,我希望能够在代码内和nib中初始化。
What's the correct way to write both initWithFrame
and initWithCoder
methods? They both share a block of code that is used for some initialization.
initWithFrame和initWithCoder方法的正确写法是什么?它们都共享用于某些初始化的代码块。
3 个解决方案
#1
56
The right thing to do in that case is to create another method containing the code that's common to both -initWithFrame:
and -initWithCoder:
, and then call that method from both -initWithFrame:
and -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;
}
Do heed the concerns outlined in Justin's answer, particularly that any subclasses must not override -commonInit
. I used that name here for its illustrative value, but you'll probably want one that's more closely tied to your class and less likely to be accidentally overridden. If you're creating a purpose-built UIView subclass that's unlikely to be subclassed itself, using a common initialization method as above is perfectly fine. If you're writing a framework for others to use, or if you don't understand the issue but want to do the safest possible thing, use a static function instead.
请注意犹斯丁回答的问题,特别是任何子类都不能重写-commonInit。我在这里使用了这个名称作为它的说明值,但是您可能想要一个与您的类更紧密地联系在一起,并且不太可能被意外覆盖的名称。如果您正在创建一个专门构建的UIView子类,它不太可能被子类化,那么使用上面所示的通用初始化方法是完全可以的。如果您正在编写一个供其他人使用的框架,或者如果您不理解这个问题,但是想要做最安全的事情,那么请使用静态函数。
#2
8
the solution is not as simple as it initially appears. there are some dangers in initialization - more on those further down. for these reasons, i generally take one of the two following approaches in objc programs:
解决方案并不像最初看起来那么简单。在初始化中存在一些危险——更多的是在更低的层次上。出于这些原因,我一般在objc程序中采用以下两种方法之一:
for trivial cases, duplication is not a bad strategy:
对于微不足道的情况,复制并不是一个坏策略:
- (id)initOne
{
self = [super init];
if (nil != self) { monIntIvar = SomeDefaultValue; }
return self;
}
- (id)initTwo
{
self = [super init];
if (nil != self) { monIntIvar = SomeDefaultValue; }
return self;
}
for nontrivial cases, i recommend a static initialization function which takes the general form:
对于非平凡的情况,我推荐一个静态初始化函数,它采用一般的形式:
// 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++:
objective - c + +:
if you're using objc++, then you can simply implement suitable default constructors for your c++ ivars and omit the majority of the initialization and dealloc scaffolding (assuming you have enabled the compiler flags correctly).
如果使用objc++,那么您可以简单地为c++ ivars实现合适的默认构造函数,并省略大部分初始化和dealloc脚手架(假设您已经正确地启用了编译器标志)。
Update:
更新:
I am going to explain why this is the safe way to initialize an object and outline some reasons why typical implementations of the other answers are dangerous.
我将解释为什么这是初始化一个对象的安全方法,并概述为什么其他答案的典型实现是危险的。
The typical problem with common initializers which call instance methods during initialization is that they abuse the inheritance graph, typically introduce complexity and bugs.
在初始化期间调用实例方法的常见初始化器的典型问题是,它们滥用了继承图,通常引入了复杂性和bug。
Recommendation: calling overridden instance methods on a partially constructed object (e.g. during initialization and dealloc) is unsafe and to be avoided. accessors are particularly bad. In other languages, this is a programmer's error (e.g. UB). Look through the objc docs on the subject (ref: "Implementing an Initializer"). I consider this a must, but I still know people who insist instance methods and accessors are better in partially constructed states because it "usually works for them".
推荐:在部分构造的对象(例如在初始化和dealloc期间)上调用重写实例方法是不安全的,应该避免。访问器尤其糟糕。在其他语言中,这是程序员的错误(例如UB)。查看关于主题的objc文档(ref:“实现初始化器”)。我认为这是必须的,但我仍然知道那些坚持在部分构造状态下实例方法和访问器更好的人,因为它“通常对他们有效”。
Requirement: Respect the inheritence graph. initialize from base up. destroy from top down. always.
要求:尊重遗传图。初始化的基础。从自顶向下破坏。总是这样。
Recommendation: keep initialization consistent for all. if your base returns something from init, you should assume all's well. don't introduce a fragile initialization dance for your clients and subclasses to implement (it's likely to come back as a bug). you need to know if you have a hold on a valid instance. also, subclassers will (rightfully) assume that your base is properly initialized when you return an object from a designated initializer. you can reduce the probability of this by making ivars of the base class private. once you return from init, clients/subclasses assume that the object they derive from is usable and initialized properly. as class graphs grow, the situation becomes very complex and bugs begin to creep out.
建议:为所有人保持初始化。如果你的基从init返回一些东西,你应该假设一切正常。不要为要实现的客户端和子类引入脆弱的初始化舞蹈(它可能会以bug的形式返回)。您需要知道是否拥有一个有效实例。此外,子类将(正确地)假设您的base在从指定的初始化器返回对象时被正确地初始化。可以通过将基类的ivars设为private来减少这种情况的发生。一旦您从init返回,客户端/子类就假定它们派生的对象是可用的,并且初始化正确。随着类图的增加,情况变得非常复杂,bug开始蔓延。
Recommendation: check for errors in init. also keep error handling and detection consistent. returning nil is the obvious convention to determine if there's been an error during initialization. detect it early.
建议:检查init中的错误。还要保持错误处理和检测的一致性。返回nil是确定初始化过程中是否有错误的明显约定。尽早检测。
Ok, but what about a shared instance method?
那么共享实例方法呢?
exmaple borrowed and altered from another post:
从另一篇文章中借用和修改:
@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;
}
// …
(btw, no error handling in that example)
(顺便说一下,在这个例子中没有错误处理)
Caleb: the greatest "danger" I see in the code above is that someone might create a subclass of the class in question, override -commonInit, and potentially initialize the object twice.
Caleb:我在上面的代码中看到的最大的“危险”是,有人可能会创建一个类的子类,重写-commonInit,并可能对对象进行两次初始化。
specifically, the subclass -[MONDragon commonInit] would be called twice (leaking resources as they would be created twice) and base's initializer and error handling would not be performed.
具体地说,子类-[MONDragon commonInit]将被调用两次(泄漏资源,因为它们将被创建两次),而base的初始化器和错误处理将不会被执行。
Caleb: If that's a real risk…
凯勒:如果那是真正的风险……
either effect can equate to an unreliable program. the problem is easily avoided by using conventional initialization.
任何一种效果都可以等同于一个不可靠的程序。使用传统的初始化方法可以很容易地避免这个问题。
Caleb: …the easiest way to deal with it is to keep -commonInit private and/or document it as something not to override
Caleb:最简单的处理方法是将-commonInit私有和/或文档作为不重写的内容。
since the runtime does not distinguish visibility when messaging, this approach is dangerous because any subclass could easily declare the same private initialization method (see below).
由于运行时不区分消息传递时的可见性,因此这种方法是危险的,因为任何子类都可以很容易地声明相同的私有初始化方法(参见下面)。
documenting a method as something you should not override exposes burdens on subclassers and introduces complexities and problems which can be avoided easily - by using other approaches. it's also error prone since the compiler won't flag it.
将方法文档化为不应该重写的内容会暴露子类的负担,并引入可以通过使用其他方法轻松避免的复杂性和问题。它也容易出错,因为编译器不会标记它。
if one insists on using an instance method, a convention which you reserve such as -[MONDragon constructMONDragon]
and -[MONKomodo constructMONKomodo]
could significantly reduce the error in the majority of cases. the initializer is likely to be visible only to the TU of the class implementation, so the compiler can flag some of our potential mistakes.
如果您坚持使用实例方法,那么您保留的约定——[MONDragon构造MONDragon]和[MONKomodo构造MONKomodo]可以在大多数情况下显著减少错误。初始化器可能只对类实现的TU可见,因此编译器可以标记一些潜在的错误。
side note: a common object constructor such as:
附注:常见的对象构造函数,如:
- (void)commonInit
{
[super commonInit];
// init this instance here
}
(which i have seen as well) is even worse because it restricts initialization, removes context (e.g. parameters), and you still end up with people mixing their initialization code across classes between designated initializer and -commonInit
.
(我也看到过)更糟糕的是,它限制了初始化,删除了上下文(例如参数),而您仍然需要在指定的初始化程序和-commonInit之间的各个类中混合他们的初始化代码。
through all that, a bunch of time wasted debugging all of the above problems from general misunderstanding and silly mistakes/oversights, i've concluded that a static function is the easiest to understand and maintain when you need to implement common initialization for a class. classes should insulate their clents from dangers, a problem the 'common initializer via instance method' has repeatedly failed at.
通过所有这些,我发现当您需要为一个类实现公共初始化时,静态函数是最容易理解和维护的。类应该使它们的类与危险隔离,这是“通过实例方法的通用初始化器”反复失败的问题。
it is not an option in the OP based on the method specified, but as a general note: you can typically consdolidate common initialization more easily using convenience constructors. this is particularly useful to minimize complexity when dealing with class clusters, classes which may return specializations, and implementations which may opt to select from multiple internal initializers.
在OP中,它不是基于指定方法的选项,而是作为一个通用的注意事项:您通常可以使用方便的构造函数更容易地遵守公共初始化。在处理类集群、可能返回专门化的类以及可能选择从多个内部初始化器中进行选择的实现时,这对于最小化复杂性特别有用。
#3
3
If they share code, just have them call a third initialization method.
如果它们共享代码,只需让它们调用第三个初始化方法。
For example, initWithFrame
might look something like this:
例如,initWithFrame可能看起来是这样的:
- (id)initWithFrame:(CGRect)frame {
if ((self = [super initWithFrame:frame])) {
[self doMyInitStuff];
}
return self;
}
Note if you're on OS X (as opposed to iOS) the frame will be NSRect
instead of CGRect
.
注意,如果你在OS X上(与iOS相反),框架将是NSRect而不是CGRect。
If you need to do error checking, have your initialization method return an error status like so:
如果需要进行错误检查,请让初始化方法返回如下的错误状态:
- (id)initWithFrame:(CGRect)frame {
if ((self = [super initWithFrame:frame])) {
if (![self doMyInitStuff]) {
[self release];
self = nil;
}
}
return self;
}
This assumes the doMyInitStuff
method returns NO
on error.
这假定doMyInitStuff方法在错误时不返回NO。
Also, if you haven't already looked at, there's a bit of documentation on initialization that might be useful to you (though it doesn't directly address this question):
另外,如果您还没有看过,有一些关于初始化的文档可能对您有用(尽管它没有直接解决这个问题):
Coding Guidelines for Cocoa: Tips and Techniques for Framework Developers
可可的编码指南:框架开发人员的提示和技术
#1
56
The right thing to do in that case is to create another method containing the code that's common to both -initWithFrame:
and -initWithCoder:
, and then call that method from both -initWithFrame:
and -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;
}
Do heed the concerns outlined in Justin's answer, particularly that any subclasses must not override -commonInit
. I used that name here for its illustrative value, but you'll probably want one that's more closely tied to your class and less likely to be accidentally overridden. If you're creating a purpose-built UIView subclass that's unlikely to be subclassed itself, using a common initialization method as above is perfectly fine. If you're writing a framework for others to use, or if you don't understand the issue but want to do the safest possible thing, use a static function instead.
请注意犹斯丁回答的问题,特别是任何子类都不能重写-commonInit。我在这里使用了这个名称作为它的说明值,但是您可能想要一个与您的类更紧密地联系在一起,并且不太可能被意外覆盖的名称。如果您正在创建一个专门构建的UIView子类,它不太可能被子类化,那么使用上面所示的通用初始化方法是完全可以的。如果您正在编写一个供其他人使用的框架,或者如果您不理解这个问题,但是想要做最安全的事情,那么请使用静态函数。
#2
8
the solution is not as simple as it initially appears. there are some dangers in initialization - more on those further down. for these reasons, i generally take one of the two following approaches in objc programs:
解决方案并不像最初看起来那么简单。在初始化中存在一些危险——更多的是在更低的层次上。出于这些原因,我一般在objc程序中采用以下两种方法之一:
for trivial cases, duplication is not a bad strategy:
对于微不足道的情况,复制并不是一个坏策略:
- (id)initOne
{
self = [super init];
if (nil != self) { monIntIvar = SomeDefaultValue; }
return self;
}
- (id)initTwo
{
self = [super init];
if (nil != self) { monIntIvar = SomeDefaultValue; }
return self;
}
for nontrivial cases, i recommend a static initialization function which takes the general form:
对于非平凡的情况,我推荐一个静态初始化函数,它采用一般的形式:
// 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++:
objective - c + +:
if you're using objc++, then you can simply implement suitable default constructors for your c++ ivars and omit the majority of the initialization and dealloc scaffolding (assuming you have enabled the compiler flags correctly).
如果使用objc++,那么您可以简单地为c++ ivars实现合适的默认构造函数,并省略大部分初始化和dealloc脚手架(假设您已经正确地启用了编译器标志)。
Update:
更新:
I am going to explain why this is the safe way to initialize an object and outline some reasons why typical implementations of the other answers are dangerous.
我将解释为什么这是初始化一个对象的安全方法,并概述为什么其他答案的典型实现是危险的。
The typical problem with common initializers which call instance methods during initialization is that they abuse the inheritance graph, typically introduce complexity and bugs.
在初始化期间调用实例方法的常见初始化器的典型问题是,它们滥用了继承图,通常引入了复杂性和bug。
Recommendation: calling overridden instance methods on a partially constructed object (e.g. during initialization and dealloc) is unsafe and to be avoided. accessors are particularly bad. In other languages, this is a programmer's error (e.g. UB). Look through the objc docs on the subject (ref: "Implementing an Initializer"). I consider this a must, but I still know people who insist instance methods and accessors are better in partially constructed states because it "usually works for them".
推荐:在部分构造的对象(例如在初始化和dealloc期间)上调用重写实例方法是不安全的,应该避免。访问器尤其糟糕。在其他语言中,这是程序员的错误(例如UB)。查看关于主题的objc文档(ref:“实现初始化器”)。我认为这是必须的,但我仍然知道那些坚持在部分构造状态下实例方法和访问器更好的人,因为它“通常对他们有效”。
Requirement: Respect the inheritence graph. initialize from base up. destroy from top down. always.
要求:尊重遗传图。初始化的基础。从自顶向下破坏。总是这样。
Recommendation: keep initialization consistent for all. if your base returns something from init, you should assume all's well. don't introduce a fragile initialization dance for your clients and subclasses to implement (it's likely to come back as a bug). you need to know if you have a hold on a valid instance. also, subclassers will (rightfully) assume that your base is properly initialized when you return an object from a designated initializer. you can reduce the probability of this by making ivars of the base class private. once you return from init, clients/subclasses assume that the object they derive from is usable and initialized properly. as class graphs grow, the situation becomes very complex and bugs begin to creep out.
建议:为所有人保持初始化。如果你的基从init返回一些东西,你应该假设一切正常。不要为要实现的客户端和子类引入脆弱的初始化舞蹈(它可能会以bug的形式返回)。您需要知道是否拥有一个有效实例。此外,子类将(正确地)假设您的base在从指定的初始化器返回对象时被正确地初始化。可以通过将基类的ivars设为private来减少这种情况的发生。一旦您从init返回,客户端/子类就假定它们派生的对象是可用的,并且初始化正确。随着类图的增加,情况变得非常复杂,bug开始蔓延。
Recommendation: check for errors in init. also keep error handling and detection consistent. returning nil is the obvious convention to determine if there's been an error during initialization. detect it early.
建议:检查init中的错误。还要保持错误处理和检测的一致性。返回nil是确定初始化过程中是否有错误的明显约定。尽早检测。
Ok, but what about a shared instance method?
那么共享实例方法呢?
exmaple borrowed and altered from another post:
从另一篇文章中借用和修改:
@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;
}
// …
(btw, no error handling in that example)
(顺便说一下,在这个例子中没有错误处理)
Caleb: the greatest "danger" I see in the code above is that someone might create a subclass of the class in question, override -commonInit, and potentially initialize the object twice.
Caleb:我在上面的代码中看到的最大的“危险”是,有人可能会创建一个类的子类,重写-commonInit,并可能对对象进行两次初始化。
specifically, the subclass -[MONDragon commonInit] would be called twice (leaking resources as they would be created twice) and base's initializer and error handling would not be performed.
具体地说,子类-[MONDragon commonInit]将被调用两次(泄漏资源,因为它们将被创建两次),而base的初始化器和错误处理将不会被执行。
Caleb: If that's a real risk…
凯勒:如果那是真正的风险……
either effect can equate to an unreliable program. the problem is easily avoided by using conventional initialization.
任何一种效果都可以等同于一个不可靠的程序。使用传统的初始化方法可以很容易地避免这个问题。
Caleb: …the easiest way to deal with it is to keep -commonInit private and/or document it as something not to override
Caleb:最简单的处理方法是将-commonInit私有和/或文档作为不重写的内容。
since the runtime does not distinguish visibility when messaging, this approach is dangerous because any subclass could easily declare the same private initialization method (see below).
由于运行时不区分消息传递时的可见性,因此这种方法是危险的,因为任何子类都可以很容易地声明相同的私有初始化方法(参见下面)。
documenting a method as something you should not override exposes burdens on subclassers and introduces complexities and problems which can be avoided easily - by using other approaches. it's also error prone since the compiler won't flag it.
将方法文档化为不应该重写的内容会暴露子类的负担,并引入可以通过使用其他方法轻松避免的复杂性和问题。它也容易出错,因为编译器不会标记它。
if one insists on using an instance method, a convention which you reserve such as -[MONDragon constructMONDragon]
and -[MONKomodo constructMONKomodo]
could significantly reduce the error in the majority of cases. the initializer is likely to be visible only to the TU of the class implementation, so the compiler can flag some of our potential mistakes.
如果您坚持使用实例方法,那么您保留的约定——[MONDragon构造MONDragon]和[MONKomodo构造MONKomodo]可以在大多数情况下显著减少错误。初始化器可能只对类实现的TU可见,因此编译器可以标记一些潜在的错误。
side note: a common object constructor such as:
附注:常见的对象构造函数,如:
- (void)commonInit
{
[super commonInit];
// init this instance here
}
(which i have seen as well) is even worse because it restricts initialization, removes context (e.g. parameters), and you still end up with people mixing their initialization code across classes between designated initializer and -commonInit
.
(我也看到过)更糟糕的是,它限制了初始化,删除了上下文(例如参数),而您仍然需要在指定的初始化程序和-commonInit之间的各个类中混合他们的初始化代码。
through all that, a bunch of time wasted debugging all of the above problems from general misunderstanding and silly mistakes/oversights, i've concluded that a static function is the easiest to understand and maintain when you need to implement common initialization for a class. classes should insulate their clents from dangers, a problem the 'common initializer via instance method' has repeatedly failed at.
通过所有这些,我发现当您需要为一个类实现公共初始化时,静态函数是最容易理解和维护的。类应该使它们的类与危险隔离,这是“通过实例方法的通用初始化器”反复失败的问题。
it is not an option in the OP based on the method specified, but as a general note: you can typically consdolidate common initialization more easily using convenience constructors. this is particularly useful to minimize complexity when dealing with class clusters, classes which may return specializations, and implementations which may opt to select from multiple internal initializers.
在OP中,它不是基于指定方法的选项,而是作为一个通用的注意事项:您通常可以使用方便的构造函数更容易地遵守公共初始化。在处理类集群、可能返回专门化的类以及可能选择从多个内部初始化器中进行选择的实现时,这对于最小化复杂性特别有用。
#3
3
If they share code, just have them call a third initialization method.
如果它们共享代码,只需让它们调用第三个初始化方法。
For example, initWithFrame
might look something like this:
例如,initWithFrame可能看起来是这样的:
- (id)initWithFrame:(CGRect)frame {
if ((self = [super initWithFrame:frame])) {
[self doMyInitStuff];
}
return self;
}
Note if you're on OS X (as opposed to iOS) the frame will be NSRect
instead of CGRect
.
注意,如果你在OS X上(与iOS相反),框架将是NSRect而不是CGRect。
If you need to do error checking, have your initialization method return an error status like so:
如果需要进行错误检查,请让初始化方法返回如下的错误状态:
- (id)initWithFrame:(CGRect)frame {
if ((self = [super initWithFrame:frame])) {
if (![self doMyInitStuff]) {
[self release];
self = nil;
}
}
return self;
}
This assumes the doMyInitStuff
method returns NO
on error.
这假定doMyInitStuff方法在错误时不返回NO。
Also, if you haven't already looked at, there's a bit of documentation on initialization that might be useful to you (though it doesn't directly address this question):
另外,如果您还没有看过,有一些关于初始化的文档可能对您有用(尽管它没有直接解决这个问题):
Coding Guidelines for Cocoa: Tips and Techniques for Framework Developers
可可的编码指南:框架开发人员的提示和技术