gtm-session-fetcher: Invalid cast in GTMSessionFetcherService

Hello

This issue was described in several tickets but solution is still not there.

There are several third-party libraries that swizzle URLSession’s delegate with their own proxy objects. For example TrustKit and SplunkMint do that. However GTMSessionFetcherService completely ignores this fact and hard-casts these proxy objects to its own class - GTMSessionFetcherSessionDelegateDispatcher:

https://github.com/google/gtm-session-fetcher/blob/c879a387e0ca4abcdff9e37eb0e826f7142342b1/Source/GTMSessionFetcherService.m#L396

    BOOL hasDispatcher = (fetcherDelegate != nil &&
                          ![fetcherDelegate isKindOfClass:[GTMSessionFetcher class]]);
    if (hasDispatcher) {
      GTMSESSION_ASSERT_DEBUG([fetcherDelegate isKindOfClass:[GTMSessionFetcherSessionDelegateDispatcher class]],
                              @"Fetcher delegate class: %@", [fetcherDelegate class]);
      return (GTMSessionFetcherSessionDelegateDispatcher *)fetcherDelegate;
    }

Obviously later we get crashes in different places due to “unrecognized selector sent to instance” exceptions.

The “dumb” solution is to check the class of object before casting it:

      if ([fetcherDelegate isKindOfClass:[GTMSessionFetcherSessionDelegateDispatcher class]]) {
        return (GTMSessionFetcherSessionDelegateDispatcher *)fetcherDelegate;
      }
      else {
        return nil;
      }

But having almost zero knowledge of how GTMSessionFetcher works, it’s hard for me to understand if this code breaks something else. Any advice from the team?

We consume this library indirectly via FirebaseAuth.

Thanks, Vlad

About this issue

  • Original URL
  • State: open
  • Created 4 years ago
  • Comments: 23 (3 by maintainers)

Most upvoted comments

I hope this will get fixed sometime in the future. For now this is a workaround we applied:

// Code to swizzle `GTMSessionFetcherService.delegateDispatcherForFetcher:` in order to fix a crash
private extension GTMSessionFetcherService {
  private static var delegateDispatcherForFetcherIsSwizzled: Bool = false

  static func swizzleDelegateDispatcherForFetcher() {
    if delegateDispatcherForFetcherIsSwizzled {
      return
    }
    delegateDispatcherForFetcherIsSwizzled = true

    // `delegateDispatcherForFetcher:` is private and so we cannot use `#selector(..)`
    let originalSelector = sel_registerName("delegateDispatcherForFetcher:")
    let newSelector = #selector(new_delegateDispatcherForFetcher)
    if let originalMethod = class_getInstanceMethod(self, originalSelector),
      let newMethod = class_getInstanceMethod(self, newSelector) {
      method_setImplementation(originalMethod, method_getImplementation(newMethod))
    }
  }

  /*
   Modified code from GTMSessionFetcherService.m:
   https://github.com/google/gtm-session-fetcher/blob/c879a387e0ca4abcdff9e37eb0e826f7142342b1/Source/GTMSessionFetcherService.m#L382

   Original code returns GTMSessionFetcherSessionDelegateDispatcher but it's a private class
   so we are returning NSObject which is its superclass.
   */
  // Internal utility. Returns a fetcher's delegate if it's a dispatcher, or nil if the fetcher
  // is its own delegate (possibly via proxy) and has no dispatcher.
  @objc
  private func new_delegateDispatcherForFetcher(_ fetcher: GTMSessionFetcher?) -> NSObject? {
    if let fetcherDelegate = fetcher?.session?.delegate,
      let delegateDispatcherClass = NSClassFromString("GTMSessionFetcherSessionDelegateDispatcher"),
      fetcherDelegate.isKind(of: delegateDispatcherClass) {
      return fetcherDelegate as? NSObject
    }
    return nil
  }
}

Just call GTMSessionFetcherService.swizzleDelegateDispatcherForFetcher() before using Firebase. Hope this will help others.

Thank you so much @oailloud. This solved my issue. I had to import #import "GTMSessionFetcherService.h" and also #import "<projectname>-Swift.h" as per this : https://stackoverflow.com/questions/58233526/no-known-class-method-for-selector-name/58233691#58233691