如何子类化没有任何指定初始值设定项的类?

时间:2016-11-15 19:39:15

标签: swift

我正在尝试继承MKPolyline。但问题是,MKPolyline没有任何指定的初始值设定项。这是我试图做的:

import MapKit

class MyPolyline: MKPolyline {
    let locations: [Location]

    init(locations: [Location]) {
        self.locations = locations

        var coordinates: [CLLocationCoordinate2D] = []
        for location in locations {
            coordinates.append(location.coordinate)
        }
        super.init(coordinates: coordinates, count: coordinates.count)
    }
}

但我在Must call a designated initializer of the superclass 'MKPolyline'来电时收到super.init错误。据我所知,MKPolyline没有任何指定的初始化程序。

我该怎么办?如何对没有任何指定初始值设定项的类进行子类化?

1 个答案:

答案 0 :(得分:2)

指定初始值设定项

如您所知,指定的初始化程序提供了一种从一组参数创建对象的方法。每个Swift对象必须至少有一个指定的初始值设定项,尽管在某些情况下Swift可以默认提供它们。

对于类,如果类的所有存储属性在其声明中提供默认值,则将提供默认初始值设定项。否则,必须由类创建者提供指定的初始化程序。但是,即使明确提供了指定的init,也不需要具有可供您访问的访问控制级别。

所以MKPolyline肯定至少有一个指定的init,但是你可能看不到它。这使得子类化实际上是不可能的,但也有其他选择。

立刻想到两种选择:

类扩展和便捷初始化程序

Swift的一个重要特性是,即使原始类在另一个模块中定义,即使是第三方模块,也可以扩展类。

您遇到的问题是MKPolyline定义了便利性,但没有公开指定的。这是一个问题,因为Swift有一个规则,指定的init必须调用其直接超类的指定init。由于这个规则,即使在扩展名中,即使指定的init也不起作用。幸运的是,Swift有方便的初始化程序。

便利性只是初始化者,最终通过在同一个类中调用指定的inits来工作。他们不会向上或向下委托,而是侧身,可以这么说。与指定的init不同,一个方便的init可以调用指定的init或另一个方便的init,只要它在同一个类中。

使用这些知识,我们可以为MKPolyline创建一个扩展,声明一个方便的init,它可以调用其他方便之一。我们可以这样做,因为在扩展内部它就像你在原始类本身一样,所以这满足了方便的同类要求。

基本上,你只需要一个带有一个带有Location数组的便捷init的扩展,将它们转换为坐标,并将它们传递给已定义的方便init MKPolyline

如果您仍希望将位置数组保存为存储属性,则会遇到另一个问题,因为扩展可能不会声明存储的属性。我们可以通过使locations计算属性简单地从已存在的getCoordinates方法中读取来解决这个问题。

以下是代码:

extension MKPolyline {

    var locations: [Location] {
        guard pointCount > 0 else { return [] }

        let defaultCoordinate = CLLocationCoordinate2D(latitude: 0.0, longitude: 0.0)
        var coordinates = [CLLocationCoordinate2D](repeating: defaultCoordinate, count: pointCount)
        getCoordinates(&coordinates, range: NSRange(location: 0, length: pointCount))

        // Assuming Location has an init that takes in a coordinate:
        return coordinates.map({ Location(coordinate: $0) })
    }

    convenience init(locations: [Location]) {

        let coordinates = locations.map({ $0.coordinate })

        self.init(coordinates: coordinates, count: coordinates.count)
    }

}

这里发生了什么。在底部,我们有一个非常类似于你已经做过的方便初始化,除了它在self上调用一个方便初始化,因为我们不在子类中。我还使用map作为从Location中拉出坐标的简单方法。

最后,我们有一个计算locations属性,它在幕后使用getCoordinates方法。我提供的实现可能看起来很奇怪,但这是必要的,因为getCoordinates函数是基于Objective-C的,并在导入Swift时使用UnsafeMutablePointer。因此,您需要首先声明具有精确长度的CLLocationCoordinate2D可变数组,然后将其传递给getCoordinates,这将填充range参数指定范围内的传递数组。 &参数之前的coordinates告诉Swift它是一个inout参数,可能会被函数变异。

但是,如果您需要locations作为存储属性才能容纳更复杂的Location对象,那么您可能需要使用下面描述的第二个选项,因为扩展名可能没有存储的属性。

包装类

这个解决方案并没有让人感觉到“快速”。和前面一样,但它是我所知道的唯一一个可以让你拥有存储属性的东西。基本上,您只需定义一个包含基础MKPolyline实例的新类:

class MyPolyline {

    let underlyingPolyline: MKPolyline

    let locations: [Location]

    init(locations: [Location]) {
        self.locations = locations

        let coordinates = locations.map { $0.coordinate }
        self.underlyingPolyline = MKPolyline(coordinates: coordinates, count: coordinates.count)
    }

}

此方法的缺点是,只要您想将MyPolyline用作MKPolyline,就需要使用myPolyline.underlyingPolyline来检索实例。我知道的唯一解决方法是使用this question接受的答案描述的方法,以便将您的类型桥接到MKPolyline,但是这使用了一个没有文档的协议,因此可能没有被Apple接受。