SwiftUI and Objective-C inter-op with Swizzling (original) (raw)

Description

First of all, let's define some terminology. I will refer to:

Using FirebaseApp in a "SwiftUI app" is very confusing and there is no guidance as to where to correctly place the FirebaseApp.configure() method call and more importantly, why place it where it should be placed.

Some of my observations from placing the FirebaseApp.configure() call in different places:

Configuration

FirebaseApp.configure() in the App's init method

Works, but some of the components from Firebase are reliant on the UIApplication.shared instance, which is not initialized at the time of the call; ❌
Setup:

@main struct FirebaseShowcaseApp: App {

init() {
    FirebaseApp.configure()
}
    
var body: some Scene {
    WindowGroup {
        ContentView()
    }
}

}

Concrete issue example from FIRAuth.protectedDataInitialization:

UIApplication *application = [applicationClass sharedApplication];

if (application) { // Initialize for phone number auth. strongSelf->_tokenManager = [[FIRAuthAPNSTokenManager alloc] initWithApplication:application];

strongSelf->_appCredentialManager =
    [[FIRAuthAppCredentialManager alloc] initWithKeychain:strongSelf->_keychainServices];

strongSelf->_notificationManager = [[FIRAuthNotificationManager alloc]
    initWithApplication:application
appCredentialManager:strongSelf->_appCredentialManager];

}

at this time and point, as far as I could investigate and test, the application is NULL and that doesn't seem to change during the lifetime of the app in this setup.
Obs: swizzling on/ off does not solve the behaviour described.

FirebaseApp.configure() in UIApplicationDelegate.application(_, didFinishLaunchingWithOptions:)

Solves the problem described above since at init time the UIApplication.shared exists. But this is where the discussion gets interesting.

@UIApplicationDelegateAdaptor(MyAppDelegate.self) var delegate

The wrapper class is: SwiftUI.AppDelegate. which looks like:

<SwiftUI.AppDelegate: 0x2839f6520>
  - super: UIResponder
    - super: NSObject
  ▿ fallbackDelegate: Optional(<MyApp.MyAppDelegate: 0x2822cdac0>)
  - mainMenuController: nil
  ▿ appNavigationAuthority: SwiftUI.AppNavigationAuthority

The issue with this wrapper class is that it's not fully interoperable with Objective-C runtime causing the swizzling to fail.

Reproducing the issue

Obs: does not seem to be an iOS specific issue since it doesn't work on iOS 15 or iOS 16 either.

Reproduction steps:

  1. create SwiftUI application;
  2. add Firebase;
  3. create @UIApplicationDelegateAdaptor(AppDelegate);
  4. call FirebaseApp.configure() from UIApplicationDelegate.application(_, didFinishLaunchingWithOptions:);

Firebase SDK Version

9.6.0

Xcode Version

14.0.1

Installation Method

Swift Package Manager

Firebase Product(s)

Analytics, Authentication, Database, DynamicLinks, Firestore, Functions, Remote Config

Targeted Platforms

iOS

Relevant Log Output

Setup: FirebaseApp.configure() called in from a SwiftUI app from UIApplicationDelegate.application(_, didFinishLaunchingWithOptions:).
From: GULAppDelegateSwizzler.createSubclassWithObject:(id)appDelegate just before

objc_setAssociatedObject(appDelegate, &kGULRealIMPBySelectorKey, [realImplementationsBySelector copy], OBJC_ASSOCIATION_RETAIN_NONATOMIC);

(lldb) po realImplementationsBySelector { "application:continueUserActivity:restorationHandler:" = "{length = 8, bytes = 0x0000000000000000}"; "application:handleEventsForBackgroundURLSession:completionHandler:" = "{length = 8, bytes = 0x0000000000000000}"; "application:openURL:options:" = "{length = 8, bytes = 0x0000000000000000}"; "application:openURL:sourceApplication:annotation:" = "{length = 8, bytes = 0x0000000000000000}"; }

If using Swift Package Manager, the project's Package.resolved

Expand Package.resolved snippet

{ "pins" : [ { "identity" : "abseil-cpp-swiftpm", "kind" : "remoteSourceControl", "location" : "https://github.com/firebase/abseil-cpp-SwiftPM.git", "state" : { "revision" : "583de9bd60f66b40e78d08599cc92036c2e7e4e1", "version" : "0.20220203.2" } }, { "identity" : "appauth-ios", "kind" : "remoteSourceControl", "location" : "https://github.com/openid/AppAuth-iOS.git", "state" : { "revision" : "3d36a58a2b736f7bc499453e996a704929b25080", "version" : "1.6.0" } }, { "identity" : "boringssl-swiftpm", "kind" : "remoteSourceControl", "location" : "https://github.com/firebase/boringssl-SwiftPM.git", "state" : { "revision" : "dd3eda2b05a3f459fc3073695ad1b28659066eab", "version" : "0.9.1" } }, { "identity" : "firebase-ios-sdk", "kind" : "remoteSourceControl", "location" : "https://github.com/firebase/firebase-ios-sdk.git", "state" : { "revision" : "7e80c25b51c2ffa238879b07fbfc5baa54bb3050", "version" : "9.6.0" } }, { "identity" : "googleappmeasurement", "kind" : "remoteSourceControl", "location" : "https://github.com/google/GoogleAppMeasurement.git", "state" : { "revision" : "c1cfde8067668027b23a42c29d11c246152fe046", "version" : "9.6.0" } }, { "identity" : "googledatatransport", "kind" : "remoteSourceControl", "location" : "https://github.com/google/GoogleDataTransport.git", "state" : { "revision" : "5056b15c5acbb90cd214fe4d6138bdf5a740e5a8", "version" : "9.2.0" } }, { "identity" : "googlesignin-ios", "kind" : "remoteSourceControl", "location" : "https://github.com/google/GoogleSignIn-iOS", "state" : { "revision" : "9450e779619fc184d360c9f7ce61023587f7e1f4", "version" : "6.2.2" } }, { "identity" : "googleutilities", "kind" : "remoteSourceControl", "location" : "https://github.com/google/GoogleUtilities.git", "state" : { "revision" : "22907832079d808e82f1182b21af58ef3880666f", "version" : "7.8.0" } }, { "identity" : "grpc-ios", "kind" : "remoteSourceControl", "location" : "https://github.com/grpc/grpc-ios.git", "state" : { "revision" : "8440b914756e0d26d4f4d054a1c1581daedfc5b6", "version" : "1.44.3-grpc" } }, { "identity" : "gtm-session-fetcher", "kind" : "remoteSourceControl", "location" : "https://github.com/google/gtm-session-fetcher.git", "state" : { "revision" : "4e9bbf2808b8fee444e84a48f5f3c12641987d3e", "version" : "1.7.2" } }, { "identity" : "gtmappauth", "kind" : "remoteSourceControl", "location" : "https://github.com/google/GTMAppAuth.git", "state" : { "revision" : "6dee0cde8a1b223737a5159e55e6b4ec16bbbdd9", "version" : "1.3.1" } }, { "identity" : "leveldb", "kind" : "remoteSourceControl", "location" : "https://github.com/firebase/leveldb.git", "state" : { "revision" : "0706abcc6b0bd9cedfbb015ba840e4a780b5159b", "version" : "1.22.2" } }, { "identity" : "nanopb", "kind" : "remoteSourceControl", "location" : "https://github.com/firebase/nanopb.git", "state" : { "revision" : "819d0a2173aff699fb8c364b6fb906f7cdb1a692", "version" : "2.30909.0" } }, { "identity" : "promises", "kind" : "remoteSourceControl", "location" : "https://github.com/google/promises.git", "state" : { "revision" : "3e4e743631e86c8c70dbc6efdc7beaa6e90fd3bb", "version" : "2.1.1" } }, { "identity" : "swift-protobuf", "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-protobuf.git", "state" : { "revision" : "b8230909dedc640294d7324d37f4c91ad3dcf177", "version" : "1.20.1" } } ], "version" : 2 }