- 已关闭**。此问题需要超过focused。当前不接受答案。
- 想要改进此问题吗?**更新此问题,使其仅关注editing this post的一个问题。
昨天关门了。
Improve this question
我正在深入研究Combine
,并尝试将逻辑从VM分离到服务,以便在应用程序中的其他地方重用。我已经使用了几个层(网络调用和获取用户位置),但面临的问题是,如果逻辑在ViewModel之外,则无法连接@Publishers
,但当逻辑在其中时,它可以完美地工作。抱歉,有很多代码...
不要忘记将Privacy - Location When In Use Usage Description
添加到Info.plist
中以请求位置
- 用户界面**
struct ContentView: View {
@ObservedObject var weatherViewModel = WeatherViewModel()
var body: some View {
VStack {
Button(action: {
weatherViewModel.fetchLocation() }) {
Text("Get Location")
}
.buttonStyle(.bordered)
Text(weatherViewModel.currentWeather?.name ?? "Nothing yet")
}
.padding()
}
}
- 用户位置**
import Foundation
import CoreLocation
import Combine
enum LocationError: Error {
case unauthorized
case unableToDetermineLocation
}
final class LocationManager: NSObject {
private let locationManager = CLLocationManager()
private var authorizationRequests: [(Result<Void, LocationError>) -> Void] = []
private var locationRequests: [(Result<CLLocation, LocationError>) -> Void] = []
override init() {
super.init()
locationManager.delegate = self
}
func requestWhenInUseAuthorization() -> Future<Void, LocationError> {
guard locationManager.authorizationStatus == .notDetermined else {
return Future { $0(.success(())) }
}
let future = Future<Void, LocationError> { completion in
self.authorizationRequests.append(completion)
}
locationManager.requestWhenInUseAuthorization()
return future
}
func requestLocation() -> Future<CLLocation, LocationError> {
guard locationManager.authorizationStatus == .authorizedAlways ||
locationManager.authorizationStatus == .authorizedWhenInUse
else {
return Future { $0(.failure(LocationError.unauthorized)) }
}
let future = Future<CLLocation, LocationError> { completion in
self.locationRequests.append(completion)
}
locationManager.requestLocation()
return future
}
private func handleLocationRequestResult(_ result: Result<CLLocation, LocationError>) {
while locationRequests.count > 0 {
let request = locationRequests.removeFirst()
request(result)
}
}
}
extension LocationManager: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
let locationError: LocationError
if let error = error as? CLError, error.code == .denied {
locationError = .unauthorized
} else {
locationError = .unableToDetermineLocation
}
handleLocationRequestResult(.failure(locationError))
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let location = locations.last {
handleLocationRequestResult(.success(location))
}
}
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
while authorizationRequests.count > 0 {
let request = authorizationRequests.removeFirst()
request(.success(()))
}
}
}
- 建立联系**
import Combine
final class NetworkingManager {
enum NetworkingError: LocalizedError {
case badURLResponse(url: URL)
case unknown
var errorDescription: String? {
switch self {
case .badURLResponse(url: let url):
return "Bad response from URL: \(url)"
case .unknown:
return "Unknown Error"
}
}
}
static func download(url: URL) -> AnyPublisher<Data, Error> {
return URLSession.shared.dataTaskPublisher(for: url)
.tryMap({ try handleURLResponse(output: $0, url: url)})
.eraseToAnyPublisher()
}
static func handleURLResponse(output: URLSession.DataTaskPublisher.Output, url: URL) throws -> Data {
guard let response = output.response as? HTTPURLResponse,
response.statusCode >= 200 && response.statusCode < 300 else {
throw NetworkingError.badURLResponse(url: url)
}
return output.data
}
static func handleCompletion(completion: Subscribers.Completion<Error>) {
switch completion {
case .finished:
break
case .failure(let error):
print(error)
}
}
}
- 型号**
struct WeatherResponseModel: Codable {
let name: String
}
- 最后是
ViewModel
。这里我想将getCurrentWeather()
的逻辑删除到WeatherService**
- 最后是
import Combine
import CoreLocation
final class WeatherViewModel: ObservableObject {
@Published var currentWeather: WeatherResponseModel?
private var cancellables = Set<AnyCancellable>()
private var weatherSubscription: AnyCancellable?
private let locationManager = LocationManager()
private let weatherService = WeatherService()
func fetchLocation() {
locationManager.requestWhenInUseAuthorization()
.flatMap { self.locationManager.requestLocation() }
.sink { _ in
} receiveValue: { location in
//MARK: Works
self.getCurrentWeather(for: location.coordinate)
//MARK: Don't work, need help here!
// self.weatherService.getWeather(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude)
// self.currentWeather = self.weatherService.weather
}
.store(in: &cancellables)
}
//MARK: Wanna remove this logic to WeatherService
private func getCurrentWeather(for coordinate: CLLocationCoordinate2D) {
guard let url = URL(string: "https://api.openweathermap.org/data/2.5/weather?lat=\(coordinate.latitude)&lon=\(coordinate.longitude)&appid=299279dbd985cab3b71f59d2c2593766") else { return }
weatherSubscription = NetworkingManager.download(url: url)
.decode(type: WeatherResponseModel.self, decoder: JSONDecoder())
.receive(on: DispatchQueue.main)
.sink(receiveCompletion: NetworkingManager.handleCompletion, receiveValue: { [weak self] (returnedWeather) in
self?.currentWeather = returnedWeather
self?.weatherSubscription?.cancel()
})
}
}
- 和WeatherService,我尝试在VM中连接其他实用程序,但未成功**
import Combine
final class WeatherService {
@Published var weather: WeatherResponseModel?
private var weatherSubscription: AnyCancellable?
public func getWeather(latitude: Double, longitude: Double) {
guard let url = URL(string: "https://api.openweathermap.org/data/2.5/weather?lat=\(latitude)&lon=\(longitude)&appid=299279dbd985cab3b71f59d2c2593766") else { return }
weatherSubscription = NetworkingManager.download(url: url)
.decode(type: WeatherResponseModel.self, decoder: JSONDecoder())
.receive(on: DispatchQueue.main)
.sink(receiveCompletion: NetworkingManager.handleCompletion, receiveValue: { [weak self] (returnedWeather) in
self?.weather = returnedWeather
self?.weatherSubscription?.cancel()
})
}
}
1条答案
按热度按时间tv6aics11#
我刚在你的代码里发现了一些东西可能是你的问题的原因。
...
The second line isn't right because self.weatherService.weather is a var that gets updated asynchronously. You should assign the value after it gets updated. For example: