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)
    }
}

但我得到必须在super.init调用中调用超类’MKPolyline’错误的指定初始化程序.据我所知,MKPolyline没有任何指定的初始化程序.

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

最佳答案 指定的初始化程序

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

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

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

立即想到两种选择:

类扩展和便捷初始化器

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

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

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

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

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

如果您仍希望将位置数组保存为存储属性,则会遇到另一个问题,因为扩展可能不会声明存储的属性.我们可以通过使位置成为简单地从已经存在的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)
    }

}

这是正在发生的事情.在底部我们有一个非常类似于你已经做过的便利init,除了它在self上调用一个方便的init,因为我们不在子类中.我还使用map作为一种更简单的方法来拉出位置的坐标.

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

但是,如果您需要将位置作为存储属性以容纳更复杂的Location对象,则可能需要使用下面描述的第二个选项,因为扩展可能没有存储属性.

包装类

这个解决方案并不像以前那样感觉’Swifty’,但它是我所知道的唯一可以让你拥有存储属性的解决方案.基本上,您只需定义一个包含底层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接受.

点赞