自定义PullToRefresh执行.sink操作时UI卡顿1秒问题求助
用自定义PullToRefresh触发全屏刷新时,UI会卡顿1秒,移除updateFitnessClasses()里的.sink后恢复正常。引发问题的代码如下:
private func updateFitnessClasses() { if let firstCompany = userCompanies.first { updateFitnessClassesInteractor .update(companyID: firstCompany.id) .combineLatest(fitnessClassesRepository.fitnessClassesSections, fitnessClassesRepository.calendarDayItems) .replaceError(with: ((), [], [])) .sink(receiveValue: { [weak self] _, calendarSections, calendarRows in self?.fitnessClassesSections = calendarSections self?.calendarRows = calendarRows }) .store(in: cancelBag) } }
其中fitnessClassesRepository.fitnessClassesSections与fitnessClassesRepository.calendarDayItems为AnyPublisher<[CalendarSection], Error>,在视图.onAppear触发的方法中使用时无卡顿。UpdateFitnessClassesInteractor的update()实现:
func update(companyID: Int) -> AnyPublisher<Void, Error> { fitnessClassCache .lastUpdateTimestamp(forCompanyID: companyID) .prefix(1) .flatMap { self.apiClient.sendRequest(.fitnessClasses(timestamp: $0, companyID: companyID)) } .map { $0.data } .map { self.fitnessClassCache.updateCache(with: $0) } .eraseToAnyPublisher() }
问题原因
卡顿的核心是UI更新操作没有在主线程执行。你用combineLatest合并的三个Publisher中,fitnessClassesRepository的两个Publisher是从数据库读取数据后在后台线程发送值的,而sink默认会在上游Publisher的线程执行闭包——这就导致你在闭包里更新绑定UI的fitnessClassesSections和calendarRows时,是在后台线程操作,直接引发UI卡顿。
而.onAppear里使用时不卡,大概率是因为当时你做了线程切换,或者上游Publisher本身是在主线程发送值。
解决方案
在.sink之前添加receive(on: DispatchQueue.main),强制将闭包执行切换到主线程,确保UI属性的更新在主线程完成:
private func updateFitnessClasses() { if let firstCompany = userCompanies.first { updateFitnessClassesInteractor .update(companyID: firstCompany.id) .combineLatest(fitnessClassesRepository.fitnessClassesSections, fitnessClassesRepository.calendarDayItems) .replaceError(with: ((), [], [])) .receive(on: DispatchQueue.main) // 新增线程切换代码 .sink(receiveValue: { [weak self] _, calendarSections, calendarRows in self?.fitnessClassesSections = calendarSections self?.calendarRows = calendarRows }) .store(in: cancelBag) } }
另外可以额外检查fitnessClassCache.updateCache(with:)方法,如果这是耗时的数据库写入操作,要确保它在后台线程执行(目前你的update()方法中map操作已经在API请求的后台线程,但如果不是的话,可添加subscribe(on: DispatchQueue.global())切换线程),避免阻塞主线程。
内容的提问来源于stack exchange,提问作者Vader20FF

