【问题标题】:Swift super initializerSwift 超级初始化器
【发布时间】:2014-09-01 01:58:25
【问题描述】:

在 Swift 中,super 的初始化器应该在当前类的所有属性都被初始化之后调用。然而,这不适用于 Objective-C init,在当前类中初始化属性之前首先调用 super init。

Swift 试图通过强制执行来防止哪些问题?为什么 Objective-C 能够避免 Swift 试图阻止的问题?

【问题讨论】:

  • 我还可能提到,在 Objective-C 中你应该在调用超类初始化器之后初始化状态是有充分理由的——因为超类初始化器可能会返回一个不同的对象它被调用的对象。因此,如果您在调用超类初始化程序之前设置了一些状态,它将被设置在错误的对象上(原来的self,而不是后来的self)。 Swift 没有这个问题,因为 Swift 中的初始化器不能返回除了被调用的对象之外的对象(从 Objective-C 导入的初始化器除外)。

标签: swift


【解决方案1】:

Swift 试图通过强制执行来防止哪些问题?

这是一个很好的问题,Objective-C 没有回避它。

问题在于,当您在初始化方法中时,该对象在技术上处于部分构造状态。 Bryan's post 是一个很好的(尽管是人为的)例子。一般的问题是,如果超类的初始化程序调用一个方法,子类可能已经覆盖了这个方法。这本身并不是一件坏事。如果被覆盖的方法假定对象是完全构造的,就会出现问题。

但是,由于对象仍处于调用初始化程序的过程中,因此情况并非如此。在对 [super init] 的调用返回并且对象的类执行其任何初始化代码之前,该对象并未完全构造。

dealloc 方法存在一个相关问题:如果您在 -dealloc 方法中调用方法,这些方法可能会假定对象是完全构造的,而实际上它可能是部分解构的。在 ARC 下这没什么大不了的,但它仍然会导致一些非常微妙的错误。

对于 Swift,我们决定通过执行此规则来避免此类问题:

当您决定调用 super 时,调用类必须已完成任何特定于类的初始化。

这条规则的一个变体是:

在调用super 的初始化程序之前,您不能调用方法。

使用此规则,您将永远不会遇到上述问题。

【讨论】:

    【解决方案2】:

    ObjC 不回避任何事情。

    对于这个 ObjC 代码,它崩溃是因为父类试图从子类访问 ivar。如果使用 Swift 规则,它可以被检测/避免。即在[super init]之前初始化所有成员

    @interface Parent : NSObject
    
    @property (readonly) int value;
    
    @end
    
    @implementation Parent
    
    - (id)init {
        self = [super init];
        if (self) {
            NSLog(@"%d", self.value); // call a method, which can be overrided by child class
        }
        return self;
    }
    
    - (int)value {
        return 42;
    }
    
    @end
    
    @interface Child : Parent
    
    @end
    
    @implementation Child {
        int *_valuePtr;
    }
    
    - (id)init {
        self = [super init]; // call self.value
        if (self) {
             // to avoid crash, move this line before [super init], but it may have other undesired effect. e.g. when [super init] return another instance
            _valuePtr = calloc(sizeof(int), 1); 
        }
        return self;
    }
    
    - (void)dealloc {
        free(_valuePtr);
    }
    
    - (int)value {
        return *_valuePtr;
    }
    
    - (void)setValue:(int)value {
        *_valuePtr = value;
    }
    
    @end
    

    【讨论】:

    • 谢谢布莱恩。为什么您决定在示例中使用指向 int 而不是 int 的指针?
    • @Boon 他使用指针是因为它很容易让它崩溃。当super 的初始化器调用-value 时,使用子类实现。但是,子类还没有完成初始化,所以_valuePtrNULL,并且取消引用NULL 指针会使您的应用程序崩溃。换句话说,-value 的子类实现假定只有在对象完全初始化时才会调用该对象。这是 Objective-C 中的一个有缺陷的假设,也是问题所在。
    猜你喜欢
    • 1970-01-01
    • 2015-05-24
    • 2017-10-26
    • 1970-01-01
    • 2015-06-28
    • 1970-01-01
    • 2018-05-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多