Objective C - 自定义视图和实现init方法?

ary*_*axt 26 objective-c initwithframe initwithcoder

我有,我希望能够来初始化一个自定义视图in-codenib.

写两种方法initWithFrameinitWithCoder方法的正确方法是什么?它们共享一块用于初始化的代码块.

Cal*_*leb 60

在这种情况下做正确的做法是创建一个包含这两者共同的代码的另一种方法-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;
}
Run Code Online (Sandbox Code Playgroud)

请注意Justin的回答中提出的问题,特别是任何子类都不能覆盖 -commonInit.我在这里使用这个名称作为它的说明价值,但你可能想要一个与你的班级更紧密联系并且不太可能被意外覆盖的名称.如果您正在创建一个专门构建的UIView子类,它不太可能自己进行子类化,那么使用上面的常见初始化方法就可以了.如果您正在为其他人编写框架,或者如果您不了解问题但想要做最安全的事情,请使用静态函数.

  • @MohammadAbdurraafay是的,如果你需要引用笔尖中的其他对象,`awakeFromNib`是要走的路,但这与初始化对象有点不同.在任何情况下,对于想要以编程方式初始化的OP来说,这不是一个好的解决方案 - 如果没有从nib文件加载对象,显然不会调用`awakeFromNib`. (2认同)

jus*_*tin 8

解决方案并不像最初出现的那么简单.在初始化中存在一些危险 - 更多的是关于那些进一步下降.由于这些原因,我通常在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;
}
Run Code Online (Sandbox Code Playgroud)

对于非平凡的情况,我建议采用静态初始化函数,它采用一般形式:

// 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
Run Code Online (Sandbox Code Playgroud)

目标C++:

如果您正在使用objc ++,那么您可以简单地为您的c ++ ivars实现合适的默认构造函数,并省略大部分初始化和dealloc搭建(假设您已正确启用了编译器标记).

更新:

我将解释为什么这是初始化对象的安全方法,并概述了为什么其他答案的典型实现是危险的一些原因.

在初始化期间调用实例方法的常见初始化器的典型问题是它们滥用继承图,通常会引入复杂性和错误.

建议:在部分构造的对象上调用重写的实例方法(例如在初始化和dealloc期间)是不安全的并且要避免.访问者特别糟糕.在其他语言中,这是程序员的错误(例如UB).查看关于该主题的objc文档(参考:"实现初始化程序").我认为这是必须的,但我仍然知道坚持实例方法和访问器的人在部分构造的状态中更好,因为它" 通常适用于他们".

要求:尊重继承图.从基础初始化.从上到下摧毁.总是.

建议:保持所有的初始化一致.如果你的基地从init返回一些东西,你应该假设一切都很好.不要为你的客户端和子类引入一个脆弱的初始化舞蹈来实现(它可能会以bug的形式出现).你需要知道你是否持有有效的实例.此外,当您从指定的初始化程序返回一个对象时,子类将(正确地)假设您的基础已正确初始化.你可以通过使基类的ivars私有来降低这种可能性.一旦从init返回,客户端/子类就会假定它们派生的对象可以正常使用和初始化.随着类图的增长,情况变得非常复杂,错误开始逐渐消失.

建议:检查init中的错误.同时保持错误处理和检测的一致性.返回nil是确定初始化期间是否存在错误的明显惯例.及早发现它.

好的,但是共享实例方法呢?

从另一个帖子中借用和改变的例子:

@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;
}

// …
Run Code Online (Sandbox Code Playgroud)

(顺便说一句,该示例中没有错误处理)

Caleb:我在上面的代码中看到的最大的"危险"是有人可能会创建有问题的类的子类,覆盖-commonInit,并可能将对象初始化两次.

具体来说,子类 - [MONDragon commonInit]将被调用两次(因为它们将被创建两次泄漏资源)并且不会执行base的初始化程序和错误处理.

迦勒:如果这是一个真正的风险......

任何一种效应都可以等同于不可靠的计划.通过使用传统的初始化很容易避免这个问题.

Caleb:...处理它的最简单方法是保持-commonInit私有和/或将其记录为不覆盖的东西

由于运行时在消息传递时不区分可见性,因此这种方法很危险,因为任何子类都可以轻松地声明相同的私有初始化方法(见下文).

将方法记录为不应覆盖的方法会暴露子类的负担,并引入可以轻松避免的复杂性和问题 - 使用其他方法.它也容易出错,因为编译器不会标记它.

如果一个人坚持使用实例方法,你保留的约定,-[MONDragon constructMONDragon]并且-[MONKomodo constructMONKomodo]可以在大多数情况下显着减少错误.初始化程序可能只对类实现的TU可见,因此编译器可以标记我们的一些潜在错误.

旁注:一个常见的对象构造函数,例如:

- (void)commonInit
{
    [super commonInit];
    // init this instance here
}
Run Code Online (Sandbox Code Playgroud)

(我已经看到了)更糟糕的是因为它限制了初始化,删除了上下文(例如参数),并且你最终仍然会让人们在指定的初始化器和类之间的类之间混合他们的初始化代码-commonInit.

通过这一切,浪费了大量的时间从一般的误解和愚蠢的错误/疏忽中调试所有上述问题,我得出结论,当你需要为一个类实现常见的初始化时,静态函数是最容易理解和维护的.类应该将它们的漏洞与危险隔离开来,这是"通过实例方法的常见初始化器"一再失败的问题.

它不是OP中基于指定方法的选项,但作为一般说明:您通常可以使用便捷构造函数更轻松地整合常见初始化.这对于在处理类集群,可能返回特化的类以及可能选择从多个内部初始化器中选择的实现时最小化复杂性特别有用.