ios 合并:将逻辑从虚拟机分离到服务[已关闭]

qc6wkl3g  于 2022-12-24  发布在  iOS
关注(0)|答案(1)|浏览(93)
    • 已关闭**。此问题需要超过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()
            })
    }
}
tv6aics1

tv6aics11#

我刚在你的代码里发现了一些东西可能是你的问题的原因。
...

    • 编辑:**
//MARK: Don't work, need help here!
//                self.weatherService.getWeather(latitude:     location.coordinate.latitude, longitude: location.coordinate.longitude)
//                self.currentWeather = self.weatherService.weather

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:

self.weatherService.$weather.receive(on: RunLoop.main)
        .sink(receiveValue: { updatedWeather in
self.currentWeather = updatedWeather
}).store(in: &cancellables) // Don't forget to store it!

相关问题