Description
When running a .NET MAUI iOS app in Release configuration with AOT compilation, the app crashes with EXC_BAD_ACCESS (SIGSEGV) when MauiCALayer or StaticCAShapeLayer is disposed on the finalizer thread. The crash occurs because the finalizer attempts to call Objective-C methods on native CALayer objects that may have already been deallocated by UIKit.
This does not occur in Debug builds (interpreter mode), only in Release builds with AOT enabled.
Affected Controls
This is a general issue affecting any MAUI control that uses MauiCALayer internally, including but not limited to:
Border (with or without CornerRadius)
BoxView
Frame
- All
Shape controls (Rectangle, Ellipse, Line, Path, Polygon, Polyline, RoundRectangle)
- Any control with
Shadow applied
- Any control with
Clip applied
- Custom handlers that use CALayer
This is not limited to specific controls - any view that creates a MauiCALayer or StaticCAShapeLayer can trigger this crash when garbage collected.
Steps to Reproduce
- Create a MAUI app with any controls that use
MauiCALayer internally (e.g., Border, BoxView, shapes, shadows)
- Build for iOS in Release configuration
- Deploy via TestFlight or Ad Hoc
- Use the app normally - navigate between pages, scroll CollectionView/ListView, etc.
- App crashes within seconds to minutes as GC collects views
Expected Behavior
The app should not crash. The finalizer should not attempt to call into native objects it does not own the lifecycle of.
Actual Behavior
The app crashes with EXC_BAD_ACCESS on the finalizer thread when garbage collection runs.
Crash Log 1 - StaticCAShapeLayer
Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS at 0x0000000a720c58f0
Termination Reason: SIGNAL 11 Segmentation fault: 11
Triggered by Thread: 3
Thread 3 Crashed:
0 libobjc.A.dylib objc_msgSend
1 PodcastApp @objc MauiCALayerAutosizeToSuperLayerBehavior.detach()
2 PodcastApp xamarin_dyn_objc_msgSend
3 PodcastApp Microsoft_Maui_ApiDefinition_Messaging__void_objc_msgSend
4 PodcastApp Microsoft_Maui_Microsoft_Maui_Platform_StaticCAShapeLayer__Dispose (StaticCAShapeLayer.cs:14)
5 PodcastApp Microsoft_iOS_Foundation_NSObject__Finalize (NSObject2.cs:306)
6 PodcastApp S_P_CoreLib_System_Runtime___Finalizer__DrainQueue (__Finalizer.cs:57)
7 PodcastApp FinalizerStart(void*) (FinalizerHelpers.cpp:83)
Crash Log 2 - MauiCALayer
Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS at 0x003ee37bca571d00
Termination Reason: SIGNAL 11 Segmentation fault: 11
Triggered by Thread: 2
Thread 2 Crashed:
0 libobjc.A.dylib objc_msgSend
1 PodcastApp xamarin_dyn_objc_msgSend
2 PodcastApp Microsoft_Maui_ApiDefinition_Messaging__void_objc_msgSend
3 PodcastApp Microsoft_Maui_Microsoft_Maui_Platform_MauiCALayer__Dispose (MauiCALayer.cs:46)
4 PodcastApp Microsoft_iOS_Foundation_NSObject__Finalize (NSObject2.cs:306)
5 PodcastApp S_P_CoreLib_System_Runtime___Finalizer__DrainQueue (__Finalizer.cs:57)
6 PodcastApp FinalizerStart(void*) (FinalizerHelpers.cpp:83)
Root Cause Analysis
The fundamental issue is that MauiCALayer.Dispose() and StaticCAShapeLayer.Dispose() call into Objective-C (objc_msgSend) during finalization, but the managed wrapper does not own the lifecycle of the native CALayer.
UIKit manages the lifecycle of CALayer objects - they are deallocated on the main thread when their owning view is removed from the view hierarchy. The managed MauiCALayer wrapper is collected separately by the .NET GC, potentially after UIKit has already deallocated the native object.
When the finalizer runs:
- The GC determines the managed
MauiCALayer wrapper is unreachable
- The finalizer thread calls
Dispose(false)
Dispose() attempts to call ObjC methods (e.g., MauiCALayerAutosizeToSuperLayerBehavior.detach())
- The native
CALayer has already been deallocated by UIKit
objc_msgSend accesses freed memory → EXC_BAD_ACCESS
This is not a threading issue that can be solved by marshalling to the main thread. The native object is already deallocated - calling into it from any thread will crash.
The issue manifests more aggressively in AOT/Release builds because:
- AOT compilation results in more aggressive GC timing
- Objects become eligible for collection sooner than in interpreter mode
- The finalizer thread runs more frequently
Proposed Fix
The Dispose() methods in MauiCALayer, StaticCAShapeLayer, and related classes should not call into native objects during finalization:
protected override void Dispose(bool disposing)
{
if (disposing)
{
// Explicit Dispose() call - safe to clean up native resources
// because the caller is responsible for ensuring the object is still valid
PerformNativeCleanup();
}
// When finalizing (!disposing), do NOT touch native objects.
// UIKit owns the CALayer lifecycle and may have already deallocated it.
// The OS will reclaim the native memory regardless.
base.Dispose(disposing);
}
This follows the standard .NET disposable pattern where the finalizer should only release unmanaged resources that the object owns, not call into external frameworks that manage their own object lifecycles.
Environment
- .NET SDK: 10.0.102
- .NET Runtime: Microsoft.NETCore.App 10.0.2
- .NET MAUI: 10.0.30 (also reproduced on 10.0.20)
- Xcode: 26.2
- Build Configuration: Release with AOT
- Distribution: TestFlight
Tested on:
| Device |
iOS Version |
Result |
| iPhone 16 Pro (iPhone17,1) |
26.2.1 |
Crash confirmed |
| iPhone 14 Plus (iPhone14,8) |
26.2 |
Crash confirmed |
| iPhone 11 Pro (iPhone12,3) |
18.7.1 |
Crash confirmed |
Tested across multiple app builds (73, 81, 83, 84) with consistent crashes.
Related Issues
Workaround
Currently no reliable workaround. The issue affects core MAUI layer infrastructure used by many controls.
Description
When running a .NET MAUI iOS app in Release configuration with AOT compilation, the app crashes with
EXC_BAD_ACCESS (SIGSEGV)whenMauiCALayerorStaticCAShapeLayeris disposed on the finalizer thread. The crash occurs because the finalizer attempts to call Objective-C methods on nativeCALayerobjects that may have already been deallocated by UIKit.This does not occur in Debug builds (interpreter mode), only in Release builds with AOT enabled.
Affected Controls
This is a general issue affecting any MAUI control that uses
MauiCALayerinternally, including but not limited to:Border(with or without CornerRadius)BoxViewFrameShapecontrols (Rectangle, Ellipse, Line, Path, Polygon, Polyline, RoundRectangle)ShadowappliedClipappliedThis is not limited to specific controls - any view that creates a
MauiCALayerorStaticCAShapeLayercan trigger this crash when garbage collected.Steps to Reproduce
MauiCALayerinternally (e.g.,Border,BoxView, shapes, shadows)Expected Behavior
The app should not crash. The finalizer should not attempt to call into native objects it does not own the lifecycle of.
Actual Behavior
The app crashes with
EXC_BAD_ACCESSon the finalizer thread when garbage collection runs.Crash Log 1 - StaticCAShapeLayer
Crash Log 2 - MauiCALayer
Root Cause Analysis
The fundamental issue is that
MauiCALayer.Dispose()andStaticCAShapeLayer.Dispose()call into Objective-C (objc_msgSend) during finalization, but the managed wrapper does not own the lifecycle of the nativeCALayer.UIKit manages the lifecycle of
CALayerobjects - they are deallocated on the main thread when their owning view is removed from the view hierarchy. The managedMauiCALayerwrapper is collected separately by the .NET GC, potentially after UIKit has already deallocated the native object.When the finalizer runs:
MauiCALayerwrapper is unreachableDispose(false)Dispose()attempts to call ObjC methods (e.g.,MauiCALayerAutosizeToSuperLayerBehavior.detach())CALayerhas already been deallocated by UIKitobjc_msgSendaccesses freed memory →EXC_BAD_ACCESSThis is not a threading issue that can be solved by marshalling to the main thread. The native object is already deallocated - calling into it from any thread will crash.
The issue manifests more aggressively in AOT/Release builds because:
Proposed Fix
The
Dispose()methods inMauiCALayer,StaticCAShapeLayer, and related classes should not call into native objects during finalization:This follows the standard .NET disposable pattern where the finalizer should only release unmanaged resources that the object owns, not call into external frameworks that manage their own object lifecycles.
Environment
Tested on:
Tested across multiple app builds (73, 81, 83, 84) with consistent crashes.
Related Issues
Workaround
Currently no reliable workaround. The issue affects core MAUI layer infrastructure used by many controls.