nic*_*ing 5 debugging dictionary swift swiftui
我有一个 SearchBar,它更新某个 Binding 字符串,该字符串是地理编码的潜在位置匹配列表。有时,当我输入一个位置时,我会得到以下信息:
libswiftCore.dylib`_swift_runtime_on_report:
-> 0x1054f7180 <+0>: pushq %rbp //Error on this line says "= Thread 1: Fatal Error: Duplicate keys
of type 'GeocodedPlacemark' were found in a Dictionary. This usually means either that the type
violates Hashable's requirements, or that members of the dictionary were mutated after
insertion."
0x1054f7181 <+1>: movq %rsp, %rbp
0x1054f7184 <+4>: popq %rbp
0x1054f7185 <+5>: retq
0x1054f7186 <+6>: nopw %cs:(%rax,%rax)
Run Code Online (Sandbox Code Playgroud)
唯一的问题是,它让我不知道错误的来源在哪里......所有这些数字中是否有一些线索或在那一行找到的“pushq”关键字可以将我引导到它所指的字典?
旁注:此错误可能在每 15 次左右的搜索中发生一次,因此非常罕见。
搜索条码如下:
import SwiftUI
import Mapbox
import MapboxGeocoder
struct SearchBar: View {
var annotation: AnnotationsVM
@State var searchText: String = ""
//@State var typing: Bool = false
@State private var showCancelButton: Bool = false
@ObservedObject var locationManager = LocationManager()
@ObservedObject var VModel : ViewModel
@Binding var searchedText: String
@Binding var showResults: Bool
@Binding var showMoreDetails: Bool
var mapStyle: URL
var body: some View {
let binding = Binding<String>(get: {
self.searchText
}, set: {
self.searchText = $0
self.searchedText = self.searchText
self.VModel.findResults(address: self.searchedText)
if self.VModel.searchResults.count >= 0 {
self.showResults = true
self.showMoreDetails = false
} else {
self.showResults = false
}
}
)
return VStack {
// Search view
HStack {
Image(systemName: "magnifyingglass")
TextField("search", text: binding, onEditingChanged: { isEditing in
self.showCancelButton = true
self.showMoreDetails = false
}, onCommit: {
if self.VModel.searchResults.first != nil {
self.annotation.addNextAnnotation(address: self.rowText(result: self.VModel.searchResults.first!).label)
self.searchedText = "\(self.rowText(result: self.VModel.searchResults.first!).label)"
}
self.showMoreDetails = false
self.showResults = false
})
Button(action: {
self.searchText = ""
self.showResults = false
}) {
Image(systemName: "xmark.circle.fill").opacity(searchText == "" ? 0.0 : 1.0)
}
}
.padding(EdgeInsets(top: 8, leading: 6, bottom: 8, trailing: 6))
}
if showCancelButton {
Button("Cancel") {
UIApplication.shared.endEditing(true) // this must be placed before the other commands here
self.searchText = ""
self.showResults = false
self.showCancelButton = false
}
}
.padding(.horizontal)
}
}
private func rowText(result: GeocodedPlacemark) -> (view: Text, label: String) {
// city is not nil
// state is not nil
// country is not nil
if result.postalAddress != nil && result.postalAddress?.city != "" && result.postalAddress?.state != "" && result.postalAddress?.country != "" {
return (Text("\(result.formattedName), \(result.postalAddress!.city), \(result.postalAddress!.state), \(result.postalAddress!.country)"), "\(result.formattedName), \(result.postalAddress!.city), \(result.postalAddress!.state), \(result.postalAddress!.country)")
}
// city is not nil
// state is not nil
// country is nil
else if result.postalAddress != nil && result.postalAddress?.city != "" && result.postalAddress?.state != "" && result.postalAddress?.country == "" {
return (Text("\(result.formattedName), \(result.postalAddress!.city), \(result.postalAddress!.state)"), "\(result.formattedName), \(result.postalAddress!.city), \(result.postalAddress!.state)")
}
// city is not nil
// state is nil
// country is nil
else if result.postalAddress != nil && result.postalAddress?.city != "" && result.postalAddress?.state == "" && result.postalAddress?.country == "" {
return (Text("\(result.formattedName), \(result.postalAddress!.city)"), "\(result.formattedName), \(result.postalAddress!.city)")
}
//More if statements to cover all the different states, this section essentially just returns the way to format the different search results in the search results view (that results view works fine btw)
}
extension UIApplication {
func endEditing(_ force: Bool) {
self.windows
.filter{$0.isKeyWindow}
.first?
.endEditing(force)
}
}
struct ResignKeyboardOnDragGesture: ViewModifier {
var gesture = DragGesture().onChanged{_ in
UIApplication.shared.endEditing(true)
}
func body(content: Content) -> some View {
content.gesture(gesture)
}
}
extension View {
func resignKeyboardOnDragGesture() -> some View {
return modifier(ResignKeyboardOnDragGesture())
}
}
Run Code Online (Sandbox Code Playgroud)
VModel 类如下:
class ViewModel: ObservableObject {
@ObservedObject var locationManager = LocationManager()
@Published var lat: Double?
@Published var lon: Double?
@Published var location: CLLocationCoordinate2D?
@Published var name: CLPlacemark?
@Published var searchResults: [GeocodedPlacemark] = []
var userLatitude: CLLocationDegrees {
return (locationManager.lastLocation?.latitude ?? 0)
}
var userLongitude: CLLocationDegrees {
return (locationManager.lastLocation?.longitude ?? 0)
}
func getLocation(from address: String, completion: @escaping (_ location: CLLocationCoordinate2D?)-> Void) {
//let geocoder = CLGeocoder()
let geocoder = Geocoder(accessToken: "pk.eyJ1Ijoibmlja2JyaW5zbWFkZSIsImEiOiJjazh4Y2dzcW4wbnJyM2ZtY2V1d20yOW4wIn0.LY1H3cf7Uz4BhAUz6JmMww")
let foptions = ForwardGeocodeOptions(query: address)
print("hit this point")
foptions.focalLocation = CLLocation(latitude: userLatitude, longitude: userLongitude)
geocoder.geocode(foptions) { (placemarks, attribution ,error) in
guard let placemarks = placemarks,
let location = placemarks.first?.location?.coordinate
else {
completion(nil)
return
}
completion(location)
}
}
func fetchCoords(address: String, completion: @escaping (Double, Double) -> Void){
self.getLocation(from: address) { coordinates in
print(coordinates ?? 0) // Print here
self.location = coordinates // Assign to a local variable for further processing
if let lat = coordinates?.latitude, let lon = coordinates?.longitude {
completion(lat, lon)
}
}
}
func findResults(address: String) {
let geocoder = Geocoder(accessToken: "pk.eyJ1Ijoibmlja2JyaW5zbWFkZSIsImEiOiJjazh4Y2dzcW4wbnJyM2ZtY2V1d20yOW4wIn0.LY1H3cf7Uz4BhAUz6JmMww")
let foptions = ForwardGeocodeOptions(query: address)
foptions.focalLocation = CLLocation(latitude: userLatitude, longitude: userLongitude)
foptions.maximumResultCount = 10
geocoder.geocode(foptions) { (placemarks, attribution ,error) in
guard let placemarks = placemarks
else {
return
}
self.searchResults = []
for placemark in placemarks {
self.searchResults.append(placemark)
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
设置了一个Swift Error Breakpoint后,就停止了在这个函数中的搜索(我猜这是一个后端MapBox函数,因为肯定不是我写的;可能是框架自带的?):
fileprivate func dataTaskWithURL(_ url: URL, completionHandler: @escaping (_ data: Data?) -> Void, errorHandler: @escaping (_ error: NSError) -> Void) -> URLSessionDataTask {
var request = URLRequest(url: url)
request.setValue(userAgent, forHTTPHeaderField: "User-Agent")
return URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let data = data else {
DispatchQueue.main.async {
if let e = error as NSError? {
errorHandler(e)
} else {
let unexpectedError = NSError(domain: MBGeocoderErrorDomain, code: -1024, userInfo: [NSLocalizedDescriptionKey : "unexpected error", NSDebugDescriptionErrorKey : "this error happens when data task return nil data and nil error, which typically is not possible"])
errorHandler(unexpectedError)
}
}
return
}
let decoder = JSONDecoder()
do {
// Handle multiple batch geocoding queries, THE ERROR IS ON THE LINE BELOW and says 'Thread 19: breakpoint 1.1'
let result = try decoder.decode([GeocodeAPIResult].self, from: data)
// Check if any of the batch geocoding queries failed
if let failedResult = result.first(where: { $0.message != nil }) {
let apiError = Geocoder.descriptiveError(["message": failedResult.message!], response: response, underlyingError: error as NSError?)
DispatchQueue.main.async {
errorHandler(apiError)
}
return
}
DispatchQueue.main.async {
completionHandler(data)
}
} catch {
// Handle single & single batch geocoding queries
do {
let result = try decoder.decode(GeocodeAPIResult.self, from: data)
// Check if geocoding query failed
if let message = result.message {
let apiError = Geocoder.descriptiveError(["message": message], response: response, underlyingError: error as NSError?)
DispatchQueue.main.async {
errorHandler(apiError)
}
return
}
DispatchQueue.main.async {
completionHandler(data)
}
} catch {
// Handle errors that don't return a message (such as a server/network error)
DispatchQueue.main.async {
errorHandler(error as NSError)
}
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
我建议首先将 iOSMapbox和MapBox GeocoderSDK 更新到最新版本 - 有时这些更新可以修复框架中的突出错误。
接下来,我建议将导致错误的geocode行包装在同步DispatchQueue块中,如下所示:
func getLocation(from address: String, completion: @escaping (_ location: CLLocationCoordinate2D?)-> Void) {
//let geocoder = CLGeocoder()
let geocoder = Geocoder(accessToken: "pk.eyJ1Ijoibmlja2JyaW5zbWFkZSIsImEiOiJjazh4Y2dzcW4wbnJyM2ZtY2V1d20yOW4wIn0.LY1H3cf7Uz4BhAUz6JmMww")
let foptions = ForwardGeocodeOptions(query: address)
print("hit this point")
foptions.focalLocation = CLLocation(latitude: userLatitude, longitude: userLongitude)
DispatchQueue.global().sync {
geocoder.geocode(foptions) { (placemarks, attribution ,error) in
guard let placemarks = placemarks,
let location = placemarks.first?.location?.coordinate
else {
completion(nil)
return
}
completion(location)
}
}
}
func findResults(address: String) {
let geocoder = Geocoder(accessToken: "pk.eyJ1Ijoibmlja2JyaW5zbWFkZSIsImEiOiJjazh4Y2dzcW4wbnJyM2ZtY2V1d20yOW4wIn0.LY1H3cf7Uz4BhAUz6JmMww")
let foptions = ForwardGeocodeOptions(query: address)
foptions.focalLocation = CLLocation(latitude: userLatitude, longitude: userLongitude)
foptions.maximumResultCount = 10
DispatchQueue.global().sync {
geocoder.geocode(foptions) { (placemarks, attribution ,error) in
guard let placemarks = placemarks else {
return
}
self.searchResults = []
for placemark in placemarks {
self.searchResults.append(placemark)
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
如果这不能解决问题,那么我建议在引发 Swift Error 断点时查看 Xcode 中堆栈框架中的各个线程 - 您可以通过点击不同的线程名称在左侧面板上执行此操作。看看这个:
如何选择线程:

一旦您可以看到每个线程的单独代码行(点击左侧的每个线程,就会显示每个文件的这一点),您可以DispatchQueue.global().sync { }在代码中为每个相关线程的冲突访问行周围添加相同的块。我概述了如何选择现在放置这些块的位置。
如果您在图中看到,对于每个线程,调用堆栈都是从下到上列出的。您只需在访问变量的DispatchQueue.global().sync { }一行周围添加块即可。data但是,如果您要访问data完成块内的变量(如地理编码函数),则DispatchQueue.global().sync { }需要绕过整个函数。
如何选择错误行Thread 1:

希望这可以帮助!:)