我正在尝试继承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
没有任何指定的初始化程序。
我该怎么办?如何对没有任何指定初始值设定项的类进行子类化?
答案 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接受。