这里发生了很多事情,所以我将引导您完成解决方案。
首先,rest 是一个糟糕的变量名。当我第一次阅读它时,我认为这是“其余部分”,就像某些东西的其余部分一样,并且正在寻找“主要”数据的位置。击键不花钱,只需输入restaurants即可。
其次,您创建一个空数组,并手动将所有这些餐馆添加到其中。相反,您可以直接从包含所有餐厅的数组文字创建一个数组。这省略了分开的需要
要回答您的直接问题,您可以使用zip 一起迭代rest 和distance,但问题是rest1、rest2、...变量,它们注定要失败。当您复制/粘贴一堆行并忘记更改其中一个时会发生什么,不小心写了rest.append(rest6); rest.append(rest6),忘记了rest7?
第三,您的findDistance(from:long:) 函数需要两个Doubles(一个纬度和一个经度),并使用它们构造一个CLLocation。但是当您查看此函数的使用位置时,您已经有了一个CLLocation,您将其分解为两个Doubles,只是为了findDistance(from:long:) 立即将它们重新组合成一个CLLocation。相反,只需让findDistance 直接使用CLLocation。
第四,Restaurants 数据类型是未命名的。这不是多个餐厅。它模拟了一家餐厅。相应地命名:Restaurant
应用这些改进(并同时修复一堆缩进),我们得到:
struct Restaurant {
var name: String
var lat: Double
var long: Double
var distance: String?
}
let restaurants = [
Restaurant(name: "English Tea House", lat: 31.461812, long: 74.272524, distance: nil),
Restaurant(name: "Cafe Barbera", lat: 31.474536, long: 74.268103, distance: nil),
Restaurant(name: "Butler's Chocolate", lat: 31.467505, long: 74.251908, distance: nil,
Restaurant(name: "Freddy's Cafe", lat: 31.461312, long: 74.272124, distance: nil),
Restaurant(name: "Arcadian Cafe", lat: 31.464536, long: 74.268603, distance: nil),
Restaurant(name: "Big Moes", lat: 31.467305, long: 74.256908, distance: nil),
]
var distances = [Double]()
for location in restaurants {
distance.append(findDistance(to: location))
}
func findDistance(to destination: CLLocation) -> Double {
let source = CLLocation(latitude: 31.461512, longitude: 74.272024)
let distanceInMeters = source.distance(from: destination)
return distanceInMeters
}
直接问题
在这里,我将说明解决直接问题所经历的过程。但是,不要使用任何这些。所有这些都是糟糕的设计,试图解决设计中的潜在缺陷。我展示它们是为了展示连续改进的样子。
现在,解决直接问题:
第一次尝试:使用zip(_:_:)
第一个解决方案可能是使用zip 并行迭代restaurants 和distances,然后从每个“距离”中改变每个restaurant。像这样的:
for (restaurant, distance) in zip(restaurants, distances) {
restaurant.distance = distance
}
但是,这行不通。由于Restaurant 是值类型,循环中的restaurant 变量是数组中的变量的副本。设置它的距离会改变副本,但不会影响数组中的原始。
第二次尝试:手动索引
我们可以通过循环遍历索引来解决这个问题,尽管方式不那么漂亮:
for i in restaurants.indices {
restaurants[i] = distances[i]
}
第三次尝试:跳过distances 数组。
第二次尝试有效,但如果 distances 的唯一目的是让我们用一堆值填充它,只是为了立即使用它们并丢弃它们,为什么我们都拥有数组呢?
for i in restaurants.indices {
restaurant.location = findDistance(to: location)
}
但是,这仍然不是很好。 Restaurant 数据类型遭受两阶段初始化,即code smell。首先,我们使用nil 位置对其进行初始化,然后将其设置为真实位置。何必?我们为什么不直接设置位置?
let distance = findDistance(to: location)
let restaurants = [
Restaurant(name: "English Tea House", lat: 31.461812, long: 74.272524, distance: distance),
Restaurant(name: "Cafe Barbera", lat: 31.474536, long: 74.268103, distance: distance),
Restaurant(name: "Butler's Chocolate", lat: 31.467505, long: 74.251908, distance: distance),
Restaurant(name: "Freddy's Cafe", lat: 31.461312, long: 74.272124, distance: distance),
Restaurant(name: "Arcadian Cafe", lat: 31.464536, long: 74.268603, distance: distance),
Restaurant(name: "Big Moes", lat: 31.467305, long: 74.256908, distance: nil),
]
但这仍然不是好的设计...
用findDistance(to:) 和Restaurant.distance 修复缺陷
findDistance(to:) 到底是做什么的?在内部,它与某个硬编码的未命名位置CLLocation(latitude: 31.461512, longitude: 74.272024) 保持一定距离。当我说someRestaurant.distance 时,我会期待与我有一段距离。如果它与某个参考点 A 有一段距离,我希望 API 改写成 someResaurant.distanceFromNorthPole 之类的东西,或者类似的东西。
此外,为什么Restaurant 的工作是存储与其他事物的距离?如果我想要restaurant.distanceToSouthPole、restaurant.distanceToEquator 怎么办? API 会变得非常臃肿,restaurant 类型最终会做得太多。如果我restaurant.distanceToMe 怎么办?我可以移动,在我移动时,预先计算的存储值如何跟上我的步伐?
解决方案是根本不存储距离。相反,提供一个 API,这种数据类型的用户可以使用它来计算到他们自己选择的任何点的距离。
struct Restaurant {
var name: String
var lat: Double
var long: Double
func distance(from other: CLLocation) -> Double {
let selfLocation = CLLocation(latitude: self.lat, longitude: self.long)
return selfLocation.distance(from: other)
}
}
let restaurants = [
Restaurant(name: "English Tea House", lat: 31.461812, long: 74.272524),
Restaurant(name: "Cafe Barbera", lat: 31.474536, long: 74.268103),
Restaurant(name: "Butler's Chocolate", lat: 31.467505, long: 74.251908),
Restaurant(name: "Freddy's Cafe", lat: 31.461312, long: 74.272124),
Restaurant(name: "Arcadian Cafe", lat: 31.464536, long: 74.268603),
Restaurant(name: "Big Moes", lat: 31.467305, long: 74.256908),
]
let pointA = CLLocation(latitude: 31.461512, longitude: 74.272024) // Name me!
// and now, whenever you need the distance to pointA:
for restaurant in restaurants {
let distanceFromA = restaurant.distance(from: pointA)
// This is for the purpose of a simple example only. Never hard code units like this,
// Use `Measurement` and `MeasurementFormatter` to create textual representations
// of measurements in the correct units, language and format for the user's locale.
print("\(restaurant.name) is \(distanceFromA) meters from \(pointA)")
}
令人惊讶的是,这仍然不是我们能做到的最好的!
不要为 lats 和 longs 存储双打
这就是CLLocation 的用途。请注意,几乎所有lat 和long 的使用都需要首先将其装箱成CLLocation。所以让我们直接存储它,而不是分离单个组件并独立传递它们。这可以防止像useLocation(lat: self.lat, long: self.long /* oops! */) 这样的错误。
struct Restaurant {
var name: String
var location: CLLocation
func distance(from other: CLLocation) -> Double {
return self.location.distance(from: other)
}
}
但是,如果您这样做,初始化程序现在需要一个 CLLocation,而不是两个单独的 lat/longDoubles。这更适合与位置 API 交互(其中CLLocation 是用于交换位置信息的“通用货币”类型),但对于像餐厅这样的硬编码位置来说更麻烦,因为所有初始化程序调用都会变得臃肿不堪致电CLLocation.init(latitude:longitude:):
let restaurants = [
Restaurant(name: "English Tea House", CLLocation(latitude: 31.461812, longitude: 74.272524)),
Restaurant(name: "Cafe Barbera", CLLocation(latitude: 31.474536, longitude: 74.268103)),
Restaurant(name: "Butler's Chocolate", CLLocation(latitude: 31.467505), longitude: 74.251908)),
Restaurant(name: "Freddy's Cafe", CLLocation(latitude: 31.461312, longitude: 74.272124)),
Restaurant(name: "Arcadian Cafe", CLLocation(latitude: 31.464536, longitude: 74.268603)),
Restaurant(name: "Big Moes", CLLocation(latitude: 31.467305, longitude: 74.256908)),
]
为了解决这个问题,我们可以将CLLocation.init(latitude:longitude:) 塞进一个小初始化器中以方便使用。我在Restaurant 的扩展中这样做,而不是直接在Restaurant 的初始声明中这样做,因为这样做会保留编译器生成的初始化程序(称为“成员初始化程序”),否则会被替换:
extension Restaurant {
init(name: String, lat: Double, long: Double) {
self.init(name: name, location: CLLocation(latitude: lat, long))
}
}
这让我们可以恢复以前的好语法:
let restaurants = [
Restaurant(name: "English Tea House", lat: 31.461812, long: 74.272524),
Restaurant(name: "Cafe Barbera", lat: 31.474536, long: 74.268103),
Restaurant(name: "Butler's Chocolate", lat: 31.467505, long: 74.251908),
Restaurant(name: "Freddy's Cafe", lat: 31.461312, long: 74.272124),
Restaurant(name: "Arcadian Cafe", lat: 31.464536, long: 74.268603),
Restaurant(name: "Big Moes", lat: 31.467305, long: 74.256908),
]
可变性
餐厅名称和位置不太可能在您的应用实例的整个生命周期内发生变化,因此无需让它们保持可变。所以让我们解决这个问题:
struct Restaurant {
var name: String
var location: CLLocation
func distance(from other: CLLocation) -> Double {
return self.location.distance(from: other)
}
}
最后...
我们已经到了最后阶段。一个命名良好的Restaurant,它不需要两阶段初始化,它为用户可能喜欢的任何点提供最新的距离数据,并且由于@,它不易受到复制粘贴错误的影响987654390@ 和 long 被粘在一起形成一个CLLocation。
struct Restaurant {
var name: String
var location: CLLocation
func distance(from other: CLLocation) -> Double {
return self.location.distance(from: other)
}
}
extension Restaurant {
init(name: String, lat: Double, long: Double) {
self.init(name: name, location: CLLocation(latitude: lat, long))
}
}
let restaurants = [
Restaurant(name: "English Tea House", lat: 31.461812, long: 74.272524),
Restaurant(name: "Cafe Barbera", lat: 31.474536, long: 74.268103),
Restaurant(name: "Butler's Chocolate", lat: 31.467505, long: 74.251908,
Restaurant(name: "Freddy's Cafe", lat: 31.461312, long: 74.272124),
Restaurant(name: "Arcadian Cafe", lat: 31.464536, long: 74.268603),
Restaurant(name: "Big Moes", lat: 31.467305, long: 74.256908),
]
let pointA = CLLocation(latitude: 31.461512, longitude: 74.272024) // Name me!
// and now, whenever you need the distance to pointA you can do this (for example):
for restaurant in restaurants {
let distanceFromA = restaurant.distance(from: pointA)
// This is for the purpose of a simple example only. Never hard code units like this,
// Use `Measurement` and `MeasurementFormatter` to create textual representations
// of measurements in the correct units, language and format for the user's locale.
print("\(restaurant.name) is \(distanceFromA) meters from \(pointA)")
}