Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SwiftUI and Objective-C inter-op with Swizzling #10417

Open
fabrizioCorut opened this issue Oct 28, 2022 · 5 comments
Open

SwiftUI and Objective-C inter-op with Swizzling #10417

fabrizioCorut opened this issue Oct 28, 2022 · 5 comments
Assignees
Labels
SwiftUI Issues directly related to Apple's SwiftUI framework

Comments

@fabrizioCorut
Copy link

fabrizioCorut commented Oct 28, 2022

Description

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

  • a "SwiftUI app" an app that declares its entry point as being the implementation of the SwiftUI.App protocol, it being marked by the new @main annotation; This app can still have AppDelegate and SceneDelegate implementations, but those will not be the entry point of the application;

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.

  • with swizzling disabled everything works as expected; ✅
  • with swizzling enabled, the UIApplicationDelegate methods do not get correctly called; ❌ the investigation that led me to this conclusion is the tap DynamicLink -> app not installed -> install app -> navigate to the actual deeplink scenario. I was simply not getting the UIApplicationDelegate.application(_:open:options:) or any other method, for what is worth, called. A couple of hours later and down the rabbit hole I have discovered that SwiftUI creates its own AppDelegate wrapper over the actual MyAppDelegate declared in my application like:
@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<GULApplicationDelegate>)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
}
@google-oss-bot
Copy link

I couldn't figure out how to label this issue, so I've labeled it for a human to triage. Hang tight.

@argzdev argzdev added SwiftUI Issues directly related to Apple's SwiftUI framework and removed needs-triage labels Oct 28, 2022
@morganchen12 morganchen12 self-assigned this Oct 28, 2022
@paulb777
Copy link
Member

@fabrizioCorut Thank you for the very thorough Issue report. In the short-term, we're working to update our documentation and in the longer term, we're exploring ways to support a more natural SwiftUI lifecycle.

@peternowell
Copy link

How do you disable swizzling? I have a SwiftUI Mac app with a custom app delegate. Sorry if this is a real novice question!

@morganchen12
Copy link
Contributor

morganchen12 commented Nov 14, 2022

Hey @peternowell, you can disable swizzling by adding the FirebaseAppDelegateProxyEnabled key with value set to NO to your app's Info.plist as described here.

For now, if you're using SwiftUI, you should disable swizzling.

@kaspesi
Copy link

kaspesi commented Feb 26, 2024

Is this still a bug? Has been open for 2 years

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
SwiftUI Issues directly related to Apple's SwiftUI framework
Projects
None yet
Development

No branches or pull requests

7 participants