为什么在Swift中不能在扩展中添加指定的初始化器?

14

我认为指定初始化器和便利初始化器唯一的区别在于,前者必须调用超类 init(如果可用)。

那么我不明白为什么我无法在扩展中添加指定初始化器,但添加便利初始化器是可以的。

为什么从扩展中调用超类初始化器的初始化方法会被视为不好的做法?

2个回答

25
让我们回顾一下什么是指定初始化器。
指定初始化器完全初始化了类引入的所有属性,并调用适当的超类初始化器来继续初始化过程,直到超类链的顶部。 摘自:Apple Inc. “The Swift Programming Language.”
class ClassA {
        private let propertyA: Int

        init(propertyA: Int) {
                self.propertyA = propertyA
        }
}

class ClassB: ClassA {
        private let propertyB: Int

        init(propertyA: Int, propertyB: Int) {
                self.propertyB = propertyB
                super.init(propertyA: propertyA)
        }
}

extension ClassB {
        // If this was a designated initializer, you need to initialize propertyB before calling a superclass initializer.
        // But propertyB is a private property that you can't access.
        // If you don't have the source code of ClassB, you will not even know there is a property called propertyB.
        // This is why we can't use extensions to add designated initializers.
        init(propertyC: Int) {
                ...
        }
}

0

我是从2023年写信的,所以可能有一些变化。所以,我们在扩展ClassB中可以访问private let propertyB。所以继续搜索更多,但对我来说问题仍然存在。

使用新信息编辑:

这是一个更好的例子。想象一下我们有

class Shape {
    var backgroundColor: UIColor?
    
    init(backgroundColor: UIColor?) {
        self.backgroundColor = backgroundColor
    }
}

class Rectangle: Shape {
    
    var width: Int = 0
    var height: Int = 0
}

由于Rectangle没有任何初始化器,它继承了Shape的init(backgroundColor:)方法,并可以按照以下方式实例化:
let rectangle = Rectangle(backgroundColor: UIColor.green)

现在想象我们可以通过类似这样的扩展添加指定的初始化器:

extension Rectangle {
    init(backgroundColor: UIColor?, width: Int, height: Int) {
        self.width = width
        self.height = height
        super.init(backgroundColor: backgroundColor)
    }
}

从现在开始,矩形类将拥有自己的指定初始化器,因此,Swift将删除Rectangle继承的init(backgroundColor:)初始化器。那么使用继承的初始化器来实例化矩形的代码会发生什么呢?它们都将无效。现在假设Shape和Rectangle是在Apple框架中定义的类。干得好。我们成功地使Apple框架无效了。因此,合理的是Swift不允许我们使用扩展添加指定初始化器。
问题仍然存在:
我们可以为结构体添加指定初始化器。这将破坏默认的成员逐一初始化器。
愚蠢的例子:
struct FirstStruct {
    var prop1: Int
    private var prop2: Int
}

extension FirstStruct {
    init(prop: Int) {
        self.prop1 = prop
        self.prop2 = prop
    }
}

现在,FirstStruct没有FirstStruct(prop1: Int, prop2: Int)的初始化器。

1
那么,在同一个文件中,我们可以通过扩展ClassB来访问私有的propertyB。这并不会显著改变情况。你的第二个部分捕捉到了函数为什么永远不能被删除的要点(并且是为什么扩展只能添加方法而不能尝试更改方法的很好例子),尽管允许通过扩展在同一个文件中使用指定的初始化器并不会导致继承的初始化器被删除。在你的第三个示例中,初始化器并没有被删除。它从未存在过,因为prop2是私有的。(移除扩展并尝试使用它。) - Rob Napier
@RobNapier 好的,我们一条一条来。也许我不是很强壮。1)只在同一个文件中。- 这是真的,我甚至都不知道这个。 - Dragonboh
@RobNapier 好的,我们逐一来看。也许我不是那么强大。1)只在同一个文件中。- 这是真的,我甚至不知道这个。 2)不明白添加功能是什么意思。当我添加新的初始化时,我只是添加功能而不改变它。 3)你说的从未存在是什么意思。我尝试了相同的事情,使用 'struct'。将其移到其他文件中。所以在 'struct' 中,我可以创建新的初始化,而成员初始化将消失。我在扩展中创建了新的指定初始化,但只初始化了一个属性,因为我没有看到私有属性,编译器告诉我“并非所有存储属性都被初始化”,为什么在类中不起作用? - Dragonboh
(3)在扩展中创建一个init不会移除自动生成的init。在您的模板中没有自动生成的init,因为prop2是私有的(在这种情况下不会自动生成)。结构体从不具有指定的初始化程序。“指定”的只用于继承。https://docs.swift.org/swift-book/documentation/the-swift-programming-language/initialization/#Designated-Initializers-and-Convenience-Initializers(此外,我强烈推荐整个页面) - Rob Napier
明白了,问题是我在Swift文档中没有找到关于不自动生成私有属性的初始化方法。也许是我阅读能力不好。但一个问题仍然存在:为什么我们可以在结构体的扩展中添加指定的初始化方法(比如说主要的初始化方法而不是便利的初始化方法),而在类中却不能呢? - Dragonboh
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接