swift 如何在textView中显示async/throw函数的返回值?

ldioqlga  于 2023-03-28  发布在  Swift
关注(0)|答案(2)|浏览(103)

这是我调用的函数,它使用纬度和经度,将它们转换为城市名和国家名,然后根据我的需要返回城市名或国家名。

import SwiftUI
import CoreLocation

struct junoWeatherEntryView: View {
    
    @ObservedObject var cityVM = allCities()
    @State private var searchTerm = "San Francisco"

    var body: some View {
        VStack{
            search
            Spacer()
            ForEach(cityVM.weather){ item in
                Text("\( try await reverseGeocode(lat: item.lat ,lan:item.lan).locality ?? "Unkown")")
            }
        }
    }
    
    func reverseGeocode(lat: Double, lan: Double) async throws -> CLPlacemark {
        let geoCoder = CLGeocoder()
        let location = CLLocation(latitude: lat, longitude: lan) // <- New York

        return try await withCheckedThrowingContinuation { continuation in
            geoCoder.reverseGeocodeLocation(location) { (placemarks, error) in
                guard
                    error == nil,
                    let placemark = placemarks?.first
                else {
                    continuation.resume(throwing: error ?? CLError(.geocodeFoundNoResult))
                    return
                }

                continuation.resume(returning: placemark)
            }
        }
    }
}

这里cityVM是@Published var weather = [WeatherResponse]()
我得到的错误是

1.Cannot pass function of type '(WeatherResponse) async throws -> Text' to parameter expecting synchronous function type
2.Invalid conversion from throwing function of type '(WeatherResponse) async throws -> Text' to non-throwing function type '(Array<WeatherResponse>.Element) -> Text'

我想在TextView中显示返回值,如何实现?

hfwmuf9z

hfwmuf9z1#

reverseGeocodeLocation异步调用其完成处理程序(即稍后)。因此,有两种常见模式:
1.使用传统的完成处理程序模式。例如:

func reverseGeocode(lat: Double, lon: Double, completion: @escaping (Result<CLPlacemark, Error>) -> Void) {
    let geocoder = CLGeocoder()
    let location = CLLocation(latitude: lat, longitude: lon) // <- New York

    geocoder.reverseGeocodeLocation(location) { (placemarks, error) in
        guard
            error == nil,
            let placemark = placemarks?.first
        else {
            completion(.failure(error ?? CLError(.geocodeFoundNoResult)))
            return
        }

        completion(.success(placemark))
    }
}

你会这样称呼它:

reverseGeocode(lat: 31, lon: 32) { result in
    switch result {
    case .failure(let error):     print(error)
    case .success(let placemark): print(placemark.locality ?? "Unknown")
    }
}

1.使用现代Swift并发,例如,

func reverseGeocode(lat: Double, lon: Double) async throws -> CLPlacemark {
    let geocoder = CLGeocoder()
    let location = CLLocation(latitude: lat, longitude: lon) // <- New York

    guard let placemark = try await geocoder.reverseGeocodeLocation(location).first else {
        throw CLError(.geocodeFoundNoResult)
    }

    return placemark
}

你会这样称呼它:

Task {
    var placemark = try await reverseGeocode(lat: 31, lon: 32)
    print(placemark.locality ?? "Unknown")
}

现在,在这两个例子中,我返回了整个CLPlacemark。你可以根据你的cityName布尔值将它们更改为只返回localitycountyString,但基本思想是一样的。使用完成处理程序或async模式来处理异步检索信息的返回。
在你修改后的问题中,你要求给出一个如何在SwiftUI中使用它的例子。例如,你可以使用.task { ... }

struct ContentView: View {
    @ObservedObject var viewModel = CityViewModel()

    var body: some View {
        VStack {
            Spacer()
            ForEach(viewModel.results) { result in
                Text(result.name)
            }
            Spacer()
        }
        .padding()
        .task {
            try? await viewModel.search()
        }
    }
}

我会将业务逻辑放在视图模型中,而不是视图中:

@MainActor
class CityViewModel: ObservableObject {
    var coordinates = [
        CLLocationCoordinate2D(latitude: 31, longitude: 32),
        CLLocationCoordinate2D(latitude: 40.7, longitude: -74),
        CLLocationCoordinate2D(latitude: 34.05, longitude: -118.25)
    ]

    @Published var results: [CityResult] = []

    private let geocoder = CLGeocoder()

    func search() async throws {
        for coordinate in coordinates {
            let placemark = try await reverseGeocode(coordinate)
            results.append(CityResult(name: placemark.locality ?? "Not found"))
        }
    }

    private func reverseGeocode(_ coordinate: CLLocationCoordinate2D) async throws -> CLPlacemark {
        let location = CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)

        guard let placemark = try await geocoder.reverseGeocodeLocation(location).first else {
            throw CLError(.geocodeFoundNoResult)
        }

        return placemark
    }
}

struct CityResult: Identifiable {
    var id: String { name }
    let name: String
}

但是不要迷失在这里的细节中,因为您的示例无疑会有所不同。我们的想法是使用.task来启动异步任务,它可以更新一些可观察的属性。

5anewei6

5anewei62#

想象一下,你有一个乐于助人的助手,他知道如何获取一个位置并返回一个城市名称给你,但他需要一段时间才能做到这一点。
现在你是一个非常忙碌的人,所以你要做一些工作,直到你需要确定一个位置。你把信息写在一张纸上,交给你的助手。他跳上自行车去寻找答案,而你继续工作。你工作,直到他回来,把答案交给你。
这就是你的函数正在做的事情。它运行到你调用geoCoder.reverseGeocodeLocation的时候,然后它把位置发送给另一个处理线程,这个线程将查找信息。当前线程继续运行而不等待答案。它在另一个线程工作的同时运行if(city name)块。
当另一个线程完成时,geoCoder.reverseGeocodeLocation将调用您的completionHandler代码。
这是并发性的基本概念,你的问题让你感到惊讶。你可能需要阅读一些苹果系统异步编码的背景阅读。

相关问题