Objective C - 自定义视图和实现 init 方法?
我有一个自定义视图,我希望能够在代码内和笔尖中初始化。
编写 initWithFrame
和 initWithCoder
方法的正确方法是什么? 它们都共享一个用于某些初始化的代码块。
I have a custom view that I want to be able to initialize both in-code
and in 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.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
在这种情况下,正确的做法是创建另一个方法,其中包含
-initWithFrame:
和-initWithCoder:
通用的代码,然后从-initWithFrame:
和-initWithCoder:
调用该方法。 code>-initWithFrame: 和-initWithCoder:
:请注意 Justin 的回答中概述的问题,特别是任何子类不得覆盖
-commonInit
。我在这里使用这个名称是因为它具有说明性的价值,但您可能会想要一个与您的类联系更紧密并且不太可能被意外覆盖的名称。如果您正在创建一个专门构建的 UIView 子类,而该子类本身不太可能被子类化,那么使用上面的通用初始化方法就完全没问题了。如果您正在编写一个框架供其他人使用,或者如果您不理解该问题但想做最安全的事情,请改用静态函数。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:
: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.解决方案并不像最初看起来那么简单。初始化存在一些危险 - 下面将详细介绍这些危险。出于这些原因,我通常在 objc 程序中采用以下两种方法之一:
对于平凡的情况,重复不是一个坏策略:
对于不平凡的情况,我建议采用以下一般形式的静态初始化函数:
Objective-C++ :
如果您使用的是 objc++,那么您可以简单地为您的 c++ ivars 实现合适的默认构造函数,并省略大部分初始化和 dealloc 脚手架(假设您已正确启用编译器标志)。
更新:
我将解释为什么这是初始化对象的安全方法,并概述为什么其他答案的典型实现是危险的一些原因。
在初始化期间调用实例方法的常见初始化程序的典型问题是它们滥用继承图,通常会引入复杂性和错误。
建议:在部分构造的对象上调用重写的实例方法(例如在初始化和释放期间)是不安全的,应避免。访问器特别糟糕。在其他语言中,这是程序员的错误(例如UB)。查看有关该主题的 objc 文档(参考:“实现初始化程序”)。我认为这是必须的,但我仍然知道有人坚持认为实例方法和访问器在部分构造的状态下更好,因为它“通常适合他们”。
要求:尊重继承图。从基础向上初始化。从上到下破坏。总是。
建议:保持所有初始化一致。如果你的基地从 init 返回一些东西,你应该假设一切都很好。不要为您的客户端和子类引入脆弱的初始化舞蹈(它可能会作为错误返回)。您需要知道您是否保留了有效的实例。此外,当您从指定的初始化器返回对象时,子类将(正确地)假设您的基类已正确初始化。您可以通过将基类的 ivars 设为私有来降低出现这种情况的可能性。一旦从 init 返回,客户端/子类就会假设它们派生的对象是可用的并且已正确初始化。随着类图的增长,情况变得非常复杂,错误开始蔓延。
建议:检查 init.c 中是否有错误。还保持错误处理和检测的一致性。返回 nil 是确定初始化期间是否出现错误的明显约定。及早发现。
好的,但是共享实例方法怎么样?
exmaple 从另一篇文章借用并更改:(
顺便说一句,该示例中没有错误处理)
具体来说,子类 -[MONDragon commonInit] 将被调用两次(泄漏资源,因为它们将被创建两次)并且基类的初始化程序和错误处理将不会执行。
这两种影响都可能等同于一个不可靠的程序。使用传统的初始化可以轻松避免该问题。
因为运行时在消息传递时不区分可见性,这种方法很危险,因为任何子类都可以轻松声明相同的私有初始化方法(见下文)。
将方法记录为不应重写的方法会增加子类化者的负担,并引入可以通过使用其他方法轻松避免的复杂性和问题。它也很容易出错,因为编译器不会标记它。
如果坚持使用实例方法,您保留的约定,例如
-[MONDragon ConstructionMONDragon]
和-[MONKomodo ConstructionMONKomodo]
可以显着减少大多数情况下的错误案例。初始化程序可能仅对类实现的 TU 可见,因此编译器可以标记一些潜在的错误。旁注:一个常见的对象构造函数,例如:(
我也见过)甚至更糟,因为它限制初始化,删除上下文(例如参数),并且最终仍然会导致人们在指定的初始化器和指定的初始化器之间跨类混合初始化代码
-commonInit
。通过这一切,由于普遍的误解和愚蠢的错误/疏忽而浪费了大量时间来调试上述所有问题,我得出的结论是,当您需要为类实现公共初始化时,静态函数是最容易理解和维护的。类应该使它们的客户端免受危险,这是“通过实例方法的通用初始化程序”多次失败的问题。
它不是基于指定方法的 OP 中的选项,但作为一般注意事项:您通常可以使用便利构造函数更轻松地合并公共初始化。这对于在处理类簇、可能返回特化的类以及可能选择从多个内部初始化器中进行选择的实现时最小化复杂性特别有用。
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:
for trivial cases, duplication is not a bad strategy:
for nontrivial cases, i recommend a static initialization function which takes the general form:
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).
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.
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".
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.
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.
Ok, but what about a shared instance method?
exmaple borrowed and altered from another post:
(btw, no error handling in that example)
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.
either effect can equate to an unreliable program. the problem is easily avoided by using conventional initialization.
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.side note: a common object constructor such as:
(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
.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.
如果他们共享代码,只需让他们调用第三个初始化方法即可。
例如,
initWithFrame
可能看起来像这样:请注意,如果您使用的是 OS X(而不是 iOS),则框架将为
NSRect
而不是CGRect< /代码>。
如果您需要进行错误检查,请让您的初始化方法返回如下错误状态:
假设
doMyInitStuff
方法在出错时返回NO
。另外,如果您还没有看过,有一些关于初始化的文档可能对您有用(尽管它没有直接解决这个问题):
Cocoa 编码指南:框架开发人员的提示和技术
If they share code, just have them call a third initialization method.
For example,
initWithFrame
might look something like this:Note if you're on OS X (as opposed to iOS) the frame will be
NSRect
instead ofCGRect
.If you need to do error checking, have your initialization method return an error status like so:
This assumes the
doMyInitStuff
method returnsNO
on error.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