SwiftUI 中来自 BindableObject 的去抖动方法调用

Posted

技术标签:

【中文标题】SwiftUI 中来自 BindableObject 的去抖动方法调用【英文标题】:Debounce method call from BindableObject in SwiftUI 【发布时间】:2019-07-23 16:32:00 【问题描述】:

我是 Swift 的新手,对 SwiftUI 更是如此。我开始创建一个小的基本项目。我使用 Github API 来获取存储库列表。

所以我创建了一个“搜索栏”,就像 SwiftUI 没有 SearchBar 组件一样。每次更改我的 Textfield 内容时,我都想执行 fetch 操作。

我不希望过于频繁地调用 fetch 方法。我决定去抖动它。我面临一个问题,我找不到/理解示例。

我尝试实施去抖动解决方案,但它不起作用,我的应用程序崩溃了。

这是我的 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: $0) 
            .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"))
                
            
        
    

我尝试使用 Timer 并每 8 秒进行一次调度和操作,但这种方法会导致我的应用程序崩溃。

更多,我真的不知道用“@objc”注释声明一个函数是否是一个好习惯......

有人可以帮我实现一个正确的方法来消除 BindableObject 中的方法吗?

提前谢谢你:)

【问题讨论】:

Combine 已经有一个 debounce 操作符。 谢谢,能不能说的更准确一点? 我会说观看有关 Combine 框架的 WWDC 2019 视频。他们使用的示例与您正在处理的示例完全相同:我们不希望在用户在文本字段中键入时经常执行的网络操作。 谢谢,我会看的:) 所以我用我的新代码编辑了我的问题。我一直在观看 WWDC 2019,但我仍然无法在“flatMap”指令中调用我的异步方法。你能给我一些线索/告诉我我的代码是否更好吗? 【参考方案1】:

我终于设法设置了去抖动。

如果它可以帮助某人,这是我的实现:

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  $0 
        .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($0) 
            .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
    

【讨论】:

这不是引入了一个保留周期(cancellableself)吗? 你能给我这段代码的完整演示或任何 github 链接吗?

以上是关于SwiftUI 中来自 BindableObject 的去抖动方法调用的主要内容,如果未能解决你的问题,请参考以下文章

在 SwiftUI 列表中呈现来自 Realm 的数据的正确方法是啥

在 SwiftUI 列表中呈现来自 Realm 的数据的正确方法是啥

来自 watchOS 中 if 条件 Text() 的 SwiftUI 运行时错误

在 SwiftUI 中显示来自 CoreData 的图像

在 swiftUI 中显示来自多个选项的工作表

如何在 SwiftUI 中显示来自 url 的图像