facebook / facebook/facebook-ios-sdk
Automatic app-event logging runs heavy main-thread work (MetadataIndexer view-walk + TimeSpentData disk I/O) on launch and lifecycle transitions
- Dominant language
- Swift
- Stars
- 8.1k
- Forks
- 3.7k
- PR merge metrics
- No merged PRs in 30d
Description
### Checklist before submitting a bug report
- [x] I've updated to the latest released version of the SDK
- [x] I've searched for existing [GitHub issues](https://github.com/facebook/facebook-ios-sdk/issues)
- [x] I've looked for existing answers on [Stack Overflow](https://facebook.stackoverflow.com), the [Facebook Developer Community Forum](https://developers.facebook.com/community/) and the [Facebook Developers Group](https://www.facebook.com/groups/fbdevelopers)
- [x] I've read the [Code of Conduct](https://github.com/facebook/facebook-ios-sdk/blob/main/CODE_OF_CONDUCT.md)
- [x] This issue is not security related and can safely be disclosed publicly on GitHub
### Xcode version
Xcode 26.5
### Facebook iOS SDK version
18.0.3
### Dependency Manager
SPM
### SDK Framework
Core
### Goals
Initialize FBSDKCoreKit on launch and use automatic app-event logging for Facebook Ads attribution, without the SDK performing multi-hundred-millisecond synchronous work on the main thread during cold launch and on every applicationDidBecomeActive / applicationWillResignActive transition.
### Expected results
With Settings.shared.isAutoLogAppEventsEnabled = true, the SDK’s automatic instrumentation (FBSDKMetadataIndexer, swizzling, time-spent session tracking) should perform its work off the main thread, or be lightweight enough not to register as a main-thread hang — especially during the cold-launch window and app lifecycle callbacks.
### Actual results
Production crash/hang analytics (Instabug/Luciq) and an Instruments Time Profiler capture both
show the SDK's automatic-logging path doing synchronous work on the **main thread**:
| Symbol (top frame) | Cost | Lifecycle |
|---|---|---|
| `-[FBSDKMetadataIndexer setupMetadataIndexing]` | ~111 ms | Cold launch |
| `FBSDKSwizzler` (swizzle install) | ~105 / 53 / 47 ms | Cold launch |
| `-[FBSDKTimeSpentData suspendTimeSpentData]` | hang hot frame (~898 occ.) | `applicationWillResignActive` |
| `-[FBSDKTimeSpentData restoreTimeSpendData]` | hang hot frame (~275 occ.) | `applicationDidBecomeActive` |
| `-[FBSDKBasicUtility persistenceFilePath:]` | hang hot frame (~255 occ.) | resign/become active |
| `-[FBSDKAppEvents applicationDidBecomeActive]` | ~27 ms | become active |
Observations:
- `setupMetadataIndexing` walks every view in every window on the main thread to attach
metadata listeners — the cost scales with the on-screen view hierarchy at launch.
- `FBSDKTimeSpentData` persists "time spent" session data to disk on
`applicationWillResignActive` and re-reads it on `applicationDidBecomeActive`, synchronously
on the main thread (`persistenceFilePath:` → file read/write).
- These appear as user-visible hangs in production aggregated over ~1,258 occurrences across
recent app versions, and remain present on our current release.
These are not crashes; they are main-thread stalls attributed to the SDK's automatic
instrumentation during the latency-sensitive launch and foreground/background transitions.
### Steps to reproduce
1. Integrate FBSDKCoreKit 18.0.3 via SPM.
2. In `application(_:didFinishLaunchingWithOptions:)`, initialize the SDK and enable automatic
logging:
```swift
FBSDKCoreKit.ApplicationDelegate.shared.application(application, didFinishLaunchingWithOptions: launchOptions)
Settings.shared.isAutoLogAppEventsEnabled = true
Settings.shared.isAdvertiserIDCollectionEnabled = true
```
3. Cold-launch the app on a physical device while attached to Instruments (Time Profiler / Hangs).
4. Observe `-[FBSDKMetadataIndexer setupMetadataIndexing]` and `FBSDKSwizzler` on the main
thread during the launch window.
5. Background the app, then foreground it. Observe `-[FBSDKTimeSpentData suspendTimeSpentData]`
(on resign) and `-[FBSDKTimeSpentData restoreTimeSpendData]` (on become active) doing
synchronous disk I/O on the main thread via `-[FBSDKBasicUtility persistenceFilePath:]`.
### Code samples & details
```swift
SDK setup (runs on the launch path, before our analytics initialization which depends on
`AppEvents.shared`):
import FBSDKCoreKit
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
FBSDKCoreKit.ApplicationDelegate.shared.application(application, didFinishLaunchingWithOptions: launchOptions)
Settings.shared.isAutoLogAppEventsEnabled = true
Settings.shared.isAdvertiserIDCollectionEnabled = true
return true
}
Additional details:
- **Xcode:** 26.5 (17F42)
- **FBSDKCoreKit:** 18.0.3 (SPM)
- **Deployment target:** iOS 17+
- **`Info.plist`:** `FacebookAdvertiserIDCollectionEnabled = true`; `FacebookAutoLogAppEventsEnabled`
not set (defaults to enabled; also enabled explicitly in code as shown above).
Questions for the SDK team:
1. Can `setupMetadataIndexing` (the view-hierarchy walk + swizzle install) be moved off the
main thread, or deferred until after the first frame, when automatic logging is enabled?
2. Can `FBSDKTimeSpentData` suspend/restore disk persistence be performed off the main thread
on the lifecycle callbacks, rather than synchronously on main?
3. Is there a supported configuration to keep core app-event logging for attribution while
disabling the codeless/metadata-indexer instrumentation specifically (without disabling
`isAutoLogAppEventsEnabled` entirely)?
A test project can be provided on request.
```
Contributor guide
Assessment
This issue has not been assessed yet.