Search code examples
iosswiftswiftuicombinepull-to-refresh

UI freezes for a second when performing .sink operation


I'm using custom PullToRefresh to refresh the whole screen when user wants. PullToRefresh performs specific operation but freezes for a second while doing so. When I remove .sink from method that is performed on refresh everything works fine.

Method that causes this bug:

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)
        }
    }

Where fitnessClassesRepository.fitnessClassesSections and fitnessClassesRepository.fitnessClassesSections are AnyPublisher<[CalendarSection], Error> constructed elsewhere. I use these two publishers in another method that fires in view's .onAppear and UI do not freeze there.
Also UpdateFitnessClassesInteractor's.update() looks like this:

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()
    }

There is an API call and then new data is saved to database from which it is later fetched in fitnessClassesRepository methods.


Solution

  • It turns out that my fitnessClassesRepository publishers - fitnessClassesSections and calendarDayItems - were performing some heavy calculations synchronous on the main thread so the UI freezed for the time of doing those calculations. Having changed these calculations to be performed on background thread by adding .receive(on: DispatchQueue.global()) in the proper place and adding .receive(on: DispatchQueue.main) in my updateFitnessClasses method fixed the problem.

    Finally, my updateFitnessClasses method looks like this:

    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)
            }
        }