在 SwiftUI 中从 BindableObject 调用 Debounce 方法
Debounce method call from BindableObject in SwiftUI
我是 Swift 的新手,对 SwiftUI 更是如此。我开始创建一个小的基本项目。我使用 Github API 来获取存储库列表。
所以我创建了一个 "Search Bar",因为 SwiftUI 没有 SearchBar 组件。每次更改我的文本字段内容时,我都想执行获取操作。
我不希望 fetch 方法被调用得太频繁。我决定去抖动它。我遇到了一个问题,我没有 find/understand 示例。
我尝试实施去抖动解决方案,但它不起作用,我的应用程序崩溃了。
这是我的 BindableObject
import SwiftUI
import Combine
class ReposStore: BindableObject {
private var service: GithubService
let didChange = PassthroughSubject<Void, Never>()
@Published var searchText: String = ""
var repos: [Repository] = [] {
didSet {
didChange.send()
}
}
var error: String = "" {
didSet {
didChange.send()
}
}
var test: String = "" {
didSet {
didChange.send()
}
}
private var cancellable: AnyCancellable? = nil
init(service: GithubService) {
self.service = service
cancellable = AnyCancellable($searchText
.removeDuplicates()
.debounce(for: 2, scheduler: DispatchQueue.main)
.flatMap { self.fetch(matching: [=10=]) }
.assign(to: \.test, on: self)
)
}
func fetch(matching query: String = "") {
print("### QUERY \(query)")
self.service.getUserRepositories(matching: query) { [weak self] result in
DispatchQueue.main.async {
print("### RESULT HERE \(result)")
switch result {
case .success(let repos): self?.repos = repos
case .failure(let error): self?.error = error.localizedDescription
}
}
}
}
}
这是我的观点
import SwiftUI
struct RepositoryList : View {
@EnvironmentObject var repoStore: ReposStore
@State private var userName: String = ""
var body: some View {
VStack {
NavigationView {
VStack(spacing: 0) {
HStack {
Image(systemName: "magnifyingglass").background(Color.blue).padding(.leading, 10.0)
TextField($repoStore.repoUser, placeholder: Text("Search")).background(Color.red)
.padding(.vertical, 4.0)
.padding(.trailing, 10.0)
}
.border(Color.secondary, width: 1, cornerRadius: 5)
.padding()
List {
ForEach(self.repoStore.repos) { repository in
NavigationLink(
destination: RepositoryDetail(repository: repository).environmentObject(self.repoStore)
) {
RepositoryRow(repository: repository)
}
}
}.navigationBarTitle(Text("Repositories"))
}
}
}
}
我尝试使用计时器并每 8 秒安排一次操作,但这种方法导致我的应用程序崩溃。
此外,我真的不知道用“@objc”注释声明一个函数是否是一个好习惯...
有人可以帮我实现一种正确的方法来对 BindableObject 中的方法进行去抖动吗?
提前谢谢你:)
我终于成功设置了去抖动。
如果它可以帮助某人,这是我的实现:
import SwiftUI
import Combine
class Store : ObservableObject {
private var cancellable: AnyCancellable? = nil
@Published var searchText: String= ""
@Published var user: User? = nil
init() {
cancellable = AnyCancellable(
$searchText.removeDuplicates()
.debounce(for: 0.8, scheduler: DispatchQueue.main)
.sink { searchText in
self.searchUser()
})
}
func searchUser() {
var urlComponents = URLComponents(string: "https://api.github.com/users/\(searchText)")!
urlComponents.queryItems = [
URLQueryItem(name: "access_token", value: EnvironmentConfiguration.shared.github_token)
]
var request = URLRequest(url: urlComponents.url!)
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
searchCancellable = URLSession.shared.send(request: request)
.decode(type: User.self, decoder: JSONDecoder())
.map { [=10=] }
.replaceError(with: nil)
.receive(on: DispatchQueue.main)
.assign(to: \.user, on: self)
}
}
extension URLSession {
func send(request: URLRequest) -> AnyPublisher<Data, URLSessionError> {
dataTaskPublisher(for: request)
.mapError { URLSessionError.urlError([=10=]) }
.flatMap { data, response -> AnyPublisher<Data, URLSessionError> in
guard let response = response as? HTTPURLResponse else {
return .fail(.invalidResponse)
}
guard 200..<300 ~= response.statusCode else {
return .fail(.serverErrorMessage(statusCode: response.statusCode,
data: data))
}
return .just(data)
}.eraseToAnyPublisher()
}
enum URLSessionError: Error {
case invalidResponse
case serverErrorMessage(statusCode: Int, data: Data)
case urlError(URLError)
}
}
extension Publisher {
static func empty() -> AnyPublisher<Output, Failure> {
return Empty()
.eraseToAnyPublisher()
}
static func just(_ output: Output) -> AnyPublisher<Output, Failure> {
return Just(output)
.catch { _ in AnyPublisher<Output, Failure>.empty() }
.eraseToAnyPublisher()
}
static func fail(_ error: Failure) -> AnyPublisher<Output, Failure> {
return Fail(error: error)
.eraseToAnyPublisher()
}
}
struct User: Hashable, Identifiable, Decodable {
var id: Int
var login: String
var avatar_url: URL
var name: String?
enum CodingKeys: String, CodingKey {
case id, login, avatar_url, name
}
}
我是 Swift 的新手,对 SwiftUI 更是如此。我开始创建一个小的基本项目。我使用 Github API 来获取存储库列表。
所以我创建了一个 "Search Bar",因为 SwiftUI 没有 SearchBar 组件。每次更改我的文本字段内容时,我都想执行获取操作。
我不希望 fetch 方法被调用得太频繁。我决定去抖动它。我遇到了一个问题,我没有 find/understand 示例。
我尝试实施去抖动解决方案,但它不起作用,我的应用程序崩溃了。
这是我的 BindableObject
import SwiftUI
import Combine
class ReposStore: BindableObject {
private var service: GithubService
let didChange = PassthroughSubject<Void, Never>()
@Published var searchText: String = ""
var repos: [Repository] = [] {
didSet {
didChange.send()
}
}
var error: String = "" {
didSet {
didChange.send()
}
}
var test: String = "" {
didSet {
didChange.send()
}
}
private var cancellable: AnyCancellable? = nil
init(service: GithubService) {
self.service = service
cancellable = AnyCancellable($searchText
.removeDuplicates()
.debounce(for: 2, scheduler: DispatchQueue.main)
.flatMap { self.fetch(matching: [=10=]) }
.assign(to: \.test, on: self)
)
}
func fetch(matching query: String = "") {
print("### QUERY \(query)")
self.service.getUserRepositories(matching: query) { [weak self] result in
DispatchQueue.main.async {
print("### RESULT HERE \(result)")
switch result {
case .success(let repos): self?.repos = repos
case .failure(let error): self?.error = error.localizedDescription
}
}
}
}
}
这是我的观点
import SwiftUI
struct RepositoryList : View {
@EnvironmentObject var repoStore: ReposStore
@State private var userName: String = ""
var body: some View {
VStack {
NavigationView {
VStack(spacing: 0) {
HStack {
Image(systemName: "magnifyingglass").background(Color.blue).padding(.leading, 10.0)
TextField($repoStore.repoUser, placeholder: Text("Search")).background(Color.red)
.padding(.vertical, 4.0)
.padding(.trailing, 10.0)
}
.border(Color.secondary, width: 1, cornerRadius: 5)
.padding()
List {
ForEach(self.repoStore.repos) { repository in
NavigationLink(
destination: RepositoryDetail(repository: repository).environmentObject(self.repoStore)
) {
RepositoryRow(repository: repository)
}
}
}.navigationBarTitle(Text("Repositories"))
}
}
}
}
我尝试使用计时器并每 8 秒安排一次操作,但这种方法导致我的应用程序崩溃。
此外,我真的不知道用“@objc”注释声明一个函数是否是一个好习惯...
有人可以帮我实现一种正确的方法来对 BindableObject 中的方法进行去抖动吗?
提前谢谢你:)
我终于成功设置了去抖动。
如果它可以帮助某人,这是我的实现:
import SwiftUI
import Combine
class Store : ObservableObject {
private var cancellable: AnyCancellable? = nil
@Published var searchText: String= ""
@Published var user: User? = nil
init() {
cancellable = AnyCancellable(
$searchText.removeDuplicates()
.debounce(for: 0.8, scheduler: DispatchQueue.main)
.sink { searchText in
self.searchUser()
})
}
func searchUser() {
var urlComponents = URLComponents(string: "https://api.github.com/users/\(searchText)")!
urlComponents.queryItems = [
URLQueryItem(name: "access_token", value: EnvironmentConfiguration.shared.github_token)
]
var request = URLRequest(url: urlComponents.url!)
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
searchCancellable = URLSession.shared.send(request: request)
.decode(type: User.self, decoder: JSONDecoder())
.map { [=10=] }
.replaceError(with: nil)
.receive(on: DispatchQueue.main)
.assign(to: \.user, on: self)
}
}
extension URLSession {
func send(request: URLRequest) -> AnyPublisher<Data, URLSessionError> {
dataTaskPublisher(for: request)
.mapError { URLSessionError.urlError([=10=]) }
.flatMap { data, response -> AnyPublisher<Data, URLSessionError> in
guard let response = response as? HTTPURLResponse else {
return .fail(.invalidResponse)
}
guard 200..<300 ~= response.statusCode else {
return .fail(.serverErrorMessage(statusCode: response.statusCode,
data: data))
}
return .just(data)
}.eraseToAnyPublisher()
}
enum URLSessionError: Error {
case invalidResponse
case serverErrorMessage(statusCode: Int, data: Data)
case urlError(URLError)
}
}
extension Publisher {
static func empty() -> AnyPublisher<Output, Failure> {
return Empty()
.eraseToAnyPublisher()
}
static func just(_ output: Output) -> AnyPublisher<Output, Failure> {
return Just(output)
.catch { _ in AnyPublisher<Output, Failure>.empty() }
.eraseToAnyPublisher()
}
static func fail(_ error: Failure) -> AnyPublisher<Output, Failure> {
return Fail(error: error)
.eraseToAnyPublisher()
}
}
struct User: Hashable, Identifiable, Decodable {
var id: Int
var login: String
var avatar_url: URL
var name: String?
enum CodingKeys: String, CodingKey {
case id, login, avatar_url, name
}
}