Search code examples
iosswiftcombineurlsession

"Receive canceled" when transform Publisher


I would like to create a module, that will refresh token if needed and repeat latest request using Combine from Apple.

For now, every part is works well, but not this one:

  public func executeRequest<T: Decodable, E: ServerErrorType>(
    _ request: HTTPRequest,
    mapper: ObjectMapper<T, E>
  ) -> AnyPublisher<(T, HTTPResponse), Error> {
        return authentificator // <- handle refresh token stuff
          .refreshToken(force: false)
          .subscribe(on: DispatchQueue.global())
          .flatMap { token in // <- on obtain token - transform it into request

---> (here)
            session.publisher(for: request, mapper: mapper, token: token) // <- create request 
---> (here)
              .tryCatch({ error -> AnyPublisher<(T, HTTPResponse), Error> in
                if let error = error as? ServerErrorType,
                   error.isAuthError {
                  
                  return authentificator
                    .refreshToken(force: true)
                    .subscribe(on: DispatchQueue.global())
                    .flatMap { token -> AnyPublisher<(T, HTTPResponse), Error> in
                      session.publisher(for: request, mapper: mapper, token: token) //<- repeat if token refreshed
                    }
                    .eraseToAnyPublisher()
                } else {
                  throw error
                }
              })
              .print()
          }
          .receive(on: DispatchQueue.main)
          .print()
          .eraseToAnyPublisher()
   }

In place where I mark (here) when I have expired token, tryCatch wont works, instead "received canceled" printed in console. I'm not sure what I did wrong. Can any on advice?


Solution

  • I found the reason - my set with AnyCancellable is become nil because ViewModel become nil due to root TabBar reinit because of SwiftUI update process. (I have a TabBar as an OptionalView in another View that is root)

      var body: some View {
        VStack {
          switch viewModel.currentFlow {
            case .onboarding:
              WelcomeView()
                .transition(.opacity)
            case .main:
              MainTabBarView() // <- reinit here cause the issue
                .transition(.opacity)
         }
        }
      }
    

    The code is completely correct for refresh token, and the possible fix is:

    • either to use @StateObject for ViewModel
    @ObservedObject private var viewModel: <MyViewModel>
    

    update to:

    @StateObject private var viewModel: <MyViewModel>
    
    • either to use same ☝️ instance of TabBar at any reload.

    possible fix:

      private lazy let mainTabBar: MainTabBarView = .init() // <- init only once
      private lazy let welcome: WelcomeView = .init()
      
      var body: some View {
        VStack {
          switch viewModel.currentFlow {
            case .onboarding:
              welcome
                .transition(.opacity)
            case .main:
              mainTabBar
                .transition(.opacity)
          }
        }
      }