URL 验证发布者
Posted
技术标签:
【中文标题】URL 验证发布者【英文标题】:URL Verification Publisher 【发布时间】:2019-09-17 21:33:09 【问题描述】:我正在尝试在 Swift 中使用 Combine 编写一个 URL 验证器,并让 SwiftUI 视图订阅它。它似乎在模拟器中运行良好,但在我的开发手机上崩溃(运行 13.1)。
场景...用户输入一个 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】: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
【讨论】:
我试过这个......没有变化。我想知道是否还有其他问题导致它。我将不得不再挖掘一下,看看是否存在线程问题。 没关系,我没有意识到我还在运行 13.0。它在 iPadOS 13.1 上在将第一个字符打入文本字段时崩溃。 单步执行我得到了一个结果:注释掉.throttle
-line,它在13.1上不起作用,至少在iPad版本上不起作用。没有它,它就在这里运行。也许他们收紧了它,只允许 RunLoop.main
或 DispatchQueue.main
这样的参数。
Removing .throttle
/replaceing the Dispatch-part with RunLoop.main
在这里为 iPadOS 13.1 修复了它。示例应用程序通过它运行良好。可能值得将整个事情报告为错误,希望他们在 13.1 beta 结束时修复 .throttle
。我没有 ios 设备可以测试它,因为我只为 iPad 开发
现在可以使用了!我删除了对.throttle
的所有引用,让示例中的东西正常工作。在我的真实项目中,还有一个使用 .throttle 的计时器,这增加了混乱。以上是关于URL 验证发布者的主要内容,如果未能解决你的问题,请参考以下文章