我正在开发一个演示应用程序,用于返回带有google places API的餐馆列表。
Restaurant
类 - 1个属性是自定义类型RestaurantDetails
。这就是挑战所在:Restaurant.swift
class Restaurant {
var id:String
var placeId:String
var name:String
var location: Location //Location + address + coordinate + distance
var phone:String?
let details : RestaurantDetails
init(id:String, placeId:String, name:String, location: Location, details: RestaurantDetails) {
self.id = id
self.placeId = placeId
self.name = name
self.location = location
self.details = details
}
convenience init(dict:[String:Any]) {
let id = dict["id"] as! String
let placeId = dict["place_id"] as! String
let name = dict["name"] as! String
let address = dict["formatted_address"] as? String
let location = Location(address: address!, json: dict["geometry"] as! [String : Any])
if let price = dict["price_level"] as? Double {
print("price => \(Price(valueDouble: price))")
}
if let rating = dict["rating"] as? Double {
print("rating => \(Rating(valueDouble: rating))")
}
self.init(id: id, placeId: placeId, name: name, location: location!, details: RestaurantDetails(json: dict)!)
}
}
每次初始化details属性都会失败
即使使用断点也没有帮助调试错误,因为自定义init?似乎没有开火。我没有看到任何语法错误
如果值存在,我通常使用guard let控制流来解析JSON格式。不成功。
断点不会引发RestaurantDetails.swift
文件
RestaurantDetails.swift
enum Price {
case cheap, expensive, `default`
init(valueDouble: Double) {
switch valueDouble {
case 0..<2: self = .cheap
case 2..<4 : self = .expensive
default: self = .default
}
}
var dollarSymbol : String {
switch self {
case .cheap: return "$"
case .expensive: return "$$"
default: return ""
}
}
}
enum Rating {
case low, fair, good, excellent, `default`
init(valueDouble: Double) {
switch valueDouble {
case 0..<2: self = .low
case 3..<4: self = .fair
case 4..<5: self = .good
case 6 : self = .excellent
default: self = .default
}
}
var starSymbol : String {
switch self {
case .low: return "⭐"
case .fair: return "⭐⭐"
case .good: return "⭐⭐⭐"
case .excellent: return "⭐⭐⭐⭐"
default: return ""
}
}
}
struct RestaurantDetails {
let price:Price
let rating:Rating // Rating (enum) (filter)
let openNow: Bool //(filter)
let types : [String]?
let photos : [NSDictionary]?
}
extension RestaurantDetails {
init?(json: [String: Any]) {
guard let price = json["price_level"] as? Double,
let rating = json["rating"] as? Double,
let openingHours = json["opening_hours"] as? NSDictionary,
let types = json["types"] as? [String],
let photos = json["photos"] as? [NSDictionary] else {
return nil
}
print("json details : \(json)")
self.price = Price(valueDouble: price)
self.rating = Rating(valueDouble: rating)
self.openNow = openingHours["open_now"] as! Bool != nil ?? false
self.types = types
self.photos = photos
}
}
我还有一个完美运行的Location.swift
文件
struct Location {
var address :String
var coordinate:(lat:Double, lng:Double)
}
extension Location {
init?(address: String, json:[String:Any]) {
guard let latitude = json["location"] as? NSDictionary, let longitude = json["location"] as? NSDictionary else { return nil }
self.init(address: address, coordinate: (lat: latitude["lat"] as! Double, lng: longitude["lng"] as! Double))
}
}
答案 0 :(得分:0)
As vadian described in his comment,导致崩溃的问题是location
有一个可用的初始化程序,你强行解包它,并且由于初始化实际上是失败的,你现在已经强制解包了一个nil值。
更广泛的问题是:当某些或所有位置数据不可用时,您(开发人员)想要发生什么,以及最佳方法是什么?
根据您对问题的描述(“使用google places api返回餐馆列表的演示应用程序”),目前尚不清楚没有可用位置数据的餐馆是否应该输入您的模型。目前还不清楚是否有一些位置数据可以不用。所以我看到三个选项:
要实现这些选项,您可能需要使用一些常用工具来处理选项:
a = b ?? c
将a
设为b
,除非b
为零,在这种情况下,a
等于c
。
guard
和if
guard let a = b else { return }
将a
设为等于b
,除非b
为零,在这种情况下从当前上下文返回。你可以在返回之前在else分支中工作。
if let a = b { /* Do work using a */ } else { /* Do other work */ }
如果b
不是nil,请输入if
分支,并将a
设为b
;如果b
为nil,请输入else
分支。
在这种情况下,您可以为Restaurant
制作一个可用的初始值设定项,由location
保护为非零:
/// Initialization fails if `location` is `nil`
convenience init?(id:String, placeId:String, name:String, location: Location?, details: RestaurantDetails) {
guard let location = location else { return nil }
self.init(id: id, placeId: placeId, name: name, location: location, details: details)
}
您可以从某些类型的缺失位置数据中恢复。例如,您可能确实需要街道地址字符串,而纬度和经度是不必要的。您可以重写Location
结构以反映:
struct Location {
var address: String
var coordinate: (lat: Double, lng: Double)?
}
extension Location {
init?(address: String, json:[String:Any]) {
let coordinate: (Double, Double)?
if let latitude = json["location"] as? NSDictionary, let longitude = json["location"] as? NSDictionary {
coordinate = (latitude, longitude)
} else {
coordinate = nil
}
self.init(address: address, coordinate: coordinate)
}
}
替代方案,lat / long可能是重要的部分,但有时可能会使用其他API从街道地址恢复它们:
struct Location {
var address: String
var coordinate: (lat: Double, lng: Double)
}
extension Location {
init?(address: String, json:[String:Any]) {
let coordinate: (Double, Double)?
if let latitude = json["location"] as? NSDictionary, let longitude = json["location"] as? NSDictionary {
coordinate = (latitude, longitude)
} else {
/* An API call that takes an address string and tries to return lat/long but might return nil */
coordinate = returnedCoordinateFromAPI
}
guard let coordinate = coordinate else { return nil }
self.init(address: address, coordinate: coordinate)
}
}
这是最简单的情况。如果location
不是必需的,请将其设为可选:
class Restaurant {
var id: String
var placeId: String
var name: String
var location: Location? //Location + address + coordinate + distance
var phone: String?
let details: RestaurantDetails
init(id:String, placeId:String, name:String, location: Location?, details: RestaurantDetails) {
self.id = id
self.placeId = placeId
self.name = name
self.location = location
self.details = details
}
convenience init?(dict:[String:Any]) {
guard let id = dict["id"] as? String,
let placeId = dict["place_id"] as? String,
let name = dict["name"] as? String else { return nil }
// Use the address from the dictionary unless it is nil, in which case substitute an empty string
let address = dict["formatted_address"] as? String ?? ""
let location = Location(address: address, json: dict["geometry"] as? [String : Any])
if let price = dict["price_level"] as? Double {
print("price => \(Price(valueDouble: price))")
}
if let rating = dict["rating"] as? Double {
print("rating => \(Rating(valueDouble: rating))")
}
guard let details = RestaurantDetails(json: dict) else { return nil }
self.init(id: id, placeId: placeId, name: name, location: location, details: details)
}
}