URL验证发布者

时间:2019-09-17 21:33:09

标签: swift swiftui combine

我正在尝试在带有合并功能的Swift中编写URL验证程序,并让SwiftUI视图对其进行订阅。在模拟器中似乎可以正常运行,但是在我的开发手机上崩溃(运行13.1)。

场景...用户键入到UITextField中,该UITextField连接到视图模型中的urlString var。更改后,我将清理字符串,创建一个URL,然后使用URLSession进行HEAD测试。所有这些都在Sim中起作用,但是在设备上点击文本字段时,它会使应用程序崩溃,并且我没有得到任何良好的堆栈跟踪。有什么想法吗?

static func testURLPublisher(string: String) -> AnyPublisher<URL?, Never> {

        let validatedURL = try? validateURL(string: string)

        guard let urlToCheck = validatedURL else {
            return Just(nil).eraseToAnyPublisher()
        }

        var request = URLRequest(url: urlToCheck)
        request.httpMethod = "HEAD"

        let publisher = URLSession.shared.dataTaskPublisher(for: request)
            .handleEvents(receiveSubscription: { _ in
                networkActivityPublisher.send(true)
            }, receiveCompletion: { _ in
                networkActivityPublisher.send(false)
            }, receiveCancel: {
                networkActivityPublisher.send(false)
            })
            .tryMap { data, response -> URL? in
                // URL Responded - Check Status Code
                guard let urlResponse = response as? HTTPURLResponse, ((urlResponse.statusCode >= 200 && urlResponse.statusCode < 400) || urlResponse.statusCode == 405) else {
                        throw URLValidatorError.serverError("Could not find the a servr at: \(urlToCheck)")
                }
                        return urlResponse.url?.absoluteURL
            }
        .catch { err in
            return Just(nil)
        }
        .eraseToAnyPublisher()
        return publisher
    }

正在使用它的视图看起来像这样...

class NewSiteViewModel: ObservableObject {

    @Published var validatedURL: URL?

    //@Published var secretKey: String?
    @Published var urlString: String = ""

    @Published var isValidURL: Bool = false

    private var cancellable = Set<AnyCancellable>()

    init() {
        $urlString
        .dropFirst(1)
            .throttle(for: 0.5, scheduler: DispatchQueue(label: "Validator"), latest: true)
            .removeDuplicates()
            .compactMap { string -> AnyPublisher<URL?, Never> in
                return URL.testURLPublisher(string: string)
            }
            .switchToLatest()
            .receive(on: RunLoop.main)
            .sink { recievedURL in
                guard let url = recievedURL else {
                    self.validatedURL = nil
                    self.isValidURL = false
                    return
                }
                self.validatedURL = url
                self.isValidURL = true

            }
            .store(in: &cancellable)
    }
}

1 个答案:

答案 0 :(得分:2)

13.1beta4不像13.0中的早期实现那样实现.throttle。现在给DispatchQueue("..")作为参数崩溃,而没有任何可用的错误文本。 RunLoop.main是一个有效的参数。

它在这里起作用。您可能必须将Transport security has blocked a cleartext HTTP中所述的Allow arbitrary Loads添加到info.plist中。可能不会在Simulator中强制执行此操作,但肯定在设备上。

您可以省略NSExceptionDomains部分。

.throttle参数有效的示例

import SwiftUI
import Combine

struct URLTesterView: View {
    @ObservedObject var model = NewSiteViewModel()

    @State var networkActivity = false

    var body: some View {
        VStack{
            TextField("url string", text: $model.urlString)
            Text("Is valid: \(model.isValidURL ? "true" : "false")")
            Text("Validated URL: \(model.validatedURL?.absoluteString ?? "")")
            Text("Network activity: \(networkActivity ? "true" : "false")")
        }.onReceive(networkActivityPublisher
            .receive(on: DispatchQueue.main)) {
            self.networkActivity = $0
        }
    }
}

class NewSiteViewModel: ObservableObject {

    @Published var validatedURL: URL?

    //@Published var secretKey: String?
    @Published var urlString: String = ""

    @Published var isValidURL: Bool = false

    private var cancellable = Set<AnyCancellable>()

    init() {
        $urlString
        .dropFirst(1)
            .throttle(for: 0.5, scheduler: RunLoop.main, latest: true)
            .removeDuplicates()
            .compactMap { string -> AnyPublisher<URL?, Never> in
                return URL.testURLPublisher(string: string)
            }
            .switchToLatest()
            .receive(on: RunLoop.main)
            .sink { recievedURL in
                guard let url = recievedURL else {
                    self.validatedURL = nil
                    self.isValidURL = false
                    return
                }
                self.validatedURL = url
                self.isValidURL = true
            }
            .store(in: &cancellable)
    }
}

func validateURL(string: String) throws -> URL {
    guard let url = URL(string: string) else {
        throw URLValidatorError.urlIsInvalid(string)
    }
    return url
}

let networkActivityPublisher = PassthroughSubject<Bool, Never>()

enum URLValidatorError: Error {
    case serverError(_ string: String)
    case urlIsInvalid(_ string: String)
}

extension URL {
    static func testURLPublisher(string: String) -> AnyPublisher<URL?, Never> {

        let validatedURL = try? validateURL(string: string)

        guard let urlToCheck = validatedURL else {
            return Just(nil).eraseToAnyPublisher()
        }

        var request = URLRequest(url: urlToCheck)
        request.httpMethod = "HEAD"

        let publisher = URLSession.shared.dataTaskPublisher(for: request)
            .handleEvents(receiveSubscription: { _ in
                networkActivityPublisher.send(true)
            }, receiveCompletion: { _ in
                networkActivityPublisher.send(false)
            }, receiveCancel: {
                networkActivityPublisher.send(false)
            })
            .tryMap { data, response -> URL? in
                // URL Responded - Check Status Code
                guard let urlResponse = response as? HTTPURLResponse, ((urlResponse.statusCode >= 200 && urlResponse.statusCode < 400) || urlResponse.statusCode == 405) else {
                        throw URLValidatorError.serverError("Could not find the a servr at: \(urlToCheck)")
                }
                        return urlResponse.url?.absoluteURL
            }
        .catch { err in
            return Just(nil)
        }
        .eraseToAnyPublisher()
        return publisher
    }
}