Reproduction project for flutter/flutter#187030 —
ArgumentError: Couldn't resolve native function 'OBJC_CLASS_$_NSArray' in 'package:objective_c/objective_c.dylib'
escaping objective_c's _getClassOrProto try/catch in a production app.
Important: This project reproduces the throw, not the escape. Here the
ArgumentErroris caught (the fallback works and the app loads). My production app, with byte-identical config, has the same error escape the catch and crash. This repo exists to show that config, so the difference can be isolated. See "What you'll observe" below.
| Flutter | 3.44.0 stable (Dart 3.12.0) |
| Platform | iOS Simulator, Debug (JIT) |
| iOS toolchain | CocoaPods, use_frameworks! + use_modular_headers! |
| SPM | disabled (flutter config enable-swift-package-manager: false) |
path_provider_foundation |
2.6.0 (forced via dependency_overrides — the FFI/objective_c version) |
objective_c |
9.4.1 (transitive) |
The full dependency set mirrors the production app so the dyld/link graph
matches, but lib/main.dart only ever calls path_provider. It also registers
both @pragma('vm:entry-point') background isolates (FCM handler +
workmanager callback) and performs the first FFI call inside a Riverpod
FutureProvider build, matching the production app's call context exactly.
flutter pub get
(cd ios && pod install)
flutter run # on an iOS Simulator, DebugThen enable an "All Exceptions" breakpoint (VS Code: "All Exceptions"; Xcode: Breakpoint Navigator → Exception Breakpoint, all exceptions). A plain "Uncaught Exceptions" breakpoint will not show anything here, because the throw is caught.
With "All Exceptions" on, execution breaks on the ArgumentError (~3 times,
for the absent class symbols), and each time the } on ArgumentError { at
objective_c/lib/src/internal.dart:139 catches it, the objc_getClass
by-name fallback runs, and the app loads — the UI shows OK docs: ….
The throw and stack are byte-identical to my production app's uncaught
crash, through the entire objective_c layer:
ArgumentError: Couldn't resolve native function 'OBJC_CLASS_$_NSArray' …
Native._ffi_resolver_function (dart:ffi-patch/ffi_patch.dart:1943)
_class_NSArray.<anonymous closure> (objective_c-9.4.1/…/objective_c_bindings_generated.dart:37076)
_getClassOrProto (objective_c-9.4.1/…/internal.dart:134) ← `final p = loader();`, inside the try
getClass (objective_c-9.4.1/…/internal.dart:153)
_class_NSArray (objective_c-9.4.1/…/objective_c_bindings_generated.dart:37074)
NSArray.fromPointer (…:1444)
NSSearchPathForDirectoriesInDomains (path_provider_foundation-2.6.0/lib/src/ffi_bindings.g.dart:29)
getApplicationDocumentsDirectory (path_provider/lib/path_provider.dart:121)
… (Riverpod async provider build → ElementWithFuture.handleFuture)
internal.dart:134 is final p = loader();, statically inside the try {
(line 133) guarded by } on ArgumentError { (line 139). In this repro that
catch fires. In the production app the same ArgumentError, at the same line,
escapes the catch and crashes — i.e. the @Native resolver error reaches
the program as if the lexical try/catch around loader() were not on the
unwind path.
The repro matches the (crashing) production app on all of these and still
catches: the objective_c.dylib binary, NativeAssetsManifest.json, the full
dependency set, Podfile / use_modular_headers!, the objective_c version
(9.4.1), launch method, the Riverpod async-provider call context, both
vm:entry-point background isolates, and --dart-define-from-file launch args.