URL 验证发布者
URL Verification Publisher
我正在尝试在 Swift 中编写一个 URL 验证程序 w/ Combine 并让 SwiftUI 视图订阅它。它似乎在模拟器中运行良好,但在我的开发中崩溃 phone (运行 13.1).
场景... 用户键入 UITextField,它连接到视图模型中的 urlString 变量。当它发生变化时,我清理字符串,创建一个 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)
}
}
13.1beta4 没有像 13.0 中的早期实现那样实现 .throttle
。现在给出 DispatchQueue("..")
作为参数在该行崩溃,没有任何可用的错误文本。一个有效的论点是 RunLoop.main
.
在这里工作。您可能必须按照 中的描述将 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 = [=10=]
}
}
}
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
}
}
我正在尝试在 Swift 中编写一个 URL 验证程序 w/ Combine 并让 SwiftUI 视图订阅它。它似乎在模拟器中运行良好,但在我的开发中崩溃 phone (运行 13.1).
场景... 用户键入 UITextField,它连接到视图模型中的 urlString 变量。当它发生变化时,我清理字符串,创建一个 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)
}
}
13.1beta4 没有像 13.0 中的早期实现那样实现 .throttle
。现在给出 DispatchQueue("..")
作为参数在该行崩溃,没有任何可用的错误文本。一个有效的论点是 RunLoop.main
.
在这里工作。您可能必须按照 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 = [=10=]
}
}
}
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
}
}