Description
CarouselViewController2 subscribes to UIDevice.OrientationDidChangeNotification using the block-based NSNotificationCenter.DefaultCenter.AddObserver(...) overload, but it does not store the returned observer token.
During teardown, it calls:
NSNotificationCenter.DefaultCenter.RemoveObserver(this, UIDevice.OrientationDidChangeNotification, null);
That does not remove the block observer returned by AddObserver, so the notification center keeps the CarouselViewController2 callback target alive after the CarouselView is popped, detached, disconnected, and forced GC runs.
A repro shows that the CarouselView, handler, and platform view collect, while CarouselViewController2 remains alive. A CollectionView control scenario fully collects under the same attach/detach path.
Steps to Reproduce
- Clone the repro branch:
git clone https://github.com/AdamEssenmacher/maui.git
cd maui
git checkout repro/carousel-view-2-observer-leak
- Build the MacCatalyst repro:
./.dotnet/dotnet build repro/carouselview2-orientation-observer-leak-20260512/CarouselView2OrientationObserverLeakRepro.csproj -f net10.0-maccatalyst -p:ValidateXcodeVersion=false
- Run the generated app:
RID=maccatalyst-x64
if [ "$(uname -m)" = "arm64" ]; then RID=maccatalyst-arm64; fi
artifacts/bin/CarouselView2OrientationObserverLeakRepro/Debug/net10.0-maccatalyst/$RID/CarouselView2OrientationObserverLeakRepro.app/Contents/MacOS/CarouselView2OrientationObserverLeakRepro
- Check the result file:
cat ~/Library/carouselview2-orientation-observer-leak-result.txt
Expected Result
After the CarouselView page is popped, detached, disconnected, and forced GC runs, the CarouselViewController2 should be collected.
Actual Result
The repro exits with code 0 and reports Leak proved: True. The CarouselView, handler, and platform view collect, but CarouselViewController2 remains alive:
ViewAlive: False
HandlerAlive: False
ControllerAlive: True
PlatformViewAlive: False
The CollectionView control scenario fully collects under the same path.
Link to public reproduction project repository
https://github.com/AdamEssenmacher/maui/tree/repro/carousel-view-2-observer-leak/repro/carouselview2-orientation-observer-leak-20260512
Version with bug
10.0.60
Is this a regression from previous behavior?
Not sure, did not test other versions
Last version that worked well
Unknown/Other
Affected platforms
iOS, macOS
Affected platform versions
No response
Did you find any workaround?
No response
Relevant log output
Description
CarouselViewController2subscribes toUIDevice.OrientationDidChangeNotificationusing the block-basedNSNotificationCenter.DefaultCenter.AddObserver(...)overload, but it does not store the returned observer token.During teardown, it calls:
That does not remove the block observer returned by
AddObserver, so the notification center keeps theCarouselViewController2callback target alive after theCarouselViewis popped, detached, disconnected, and forced GC runs.A repro shows that the
CarouselView, handler, and platform view collect, whileCarouselViewController2remains alive. A CollectionView control scenario fully collects under the same attach/detach path.Steps to Reproduce
git clone https://github.com/AdamEssenmacher/maui.git cd maui git checkout repro/carousel-view-2-observer-leakcat ~/Library/carouselview2-orientation-observer-leak-result.txtExpected Result
After the
CarouselViewpage is popped, detached, disconnected, and forced GC runs, theCarouselViewController2should be collected.Actual Result
The repro exits with code
0and reportsLeak proved: True. TheCarouselView, handler, and platform view collect, butCarouselViewController2remains alive:The CollectionView control scenario fully collects under the same path.
Link to public reproduction project repository
https://github.com/AdamEssenmacher/maui/tree/repro/carousel-view-2-observer-leak/repro/carouselview2-orientation-observer-leak-20260512
Version with bug
10.0.60
Is this a regression from previous behavior?
Not sure, did not test other versions
Last version that worked well
Unknown/Other
Affected platforms
iOS, macOS
Affected platform versions
No response
Did you find any workaround?
No response
Relevant log output