maui icon indicating copy to clipboard operation
maui copied to clipboard

Avoid KVO on CALayer by introducing an Apple PlatformInterop

Open albyrock87 opened this issue 5 months ago • 9 comments

Description of Change

On a recent macios workload, a regression caused issues with WeakReferences bringing lots of crashes in our app, all tied to KVOs.

It potentially causes an issue with hot-reload which may or may not be related.

System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'Microsoft.Maui.Platform.StaticCAShapeLayer'.
   at ObjCRuntime.ThrowHelper.ThrowObjectDisposedException(Object o) in /Users/builder/azdo/_work/2/s/macios/src/ObjCRuntime/ThrowHelper.cs:line 52
   at Foundation.NSObject.get_SuperHandle() in /Users/builder/azdo/_work/2/s/macios/src/Foundation/NSObject2.cs:line 660
   at CoreAnimation.CALayer.RemoveFromSuperLayer() in /Users/builder/azdo/_work/2/s/macios/src/build/dotnet/ios/generated-sources/CoreAnimation/CALayer.g.cs:line 783
   at Microsoft.Maui.Platform.StaticCAShapeLayer.RemoveFromSuperLayer()
   at Microsoft.Maui.Platform.ContentView.RemoveContentMask()
   at Microsoft.Maui.Platform.ContentView.WillRemoveSubview(UIView uiview)
   at Foundation.NSObject.ReleaseManagedRef() in /Users/builder/azdo/_work/2/s/macios/src/Foundation/NSObject2.cs:line 458
   at Foundation.NSObject.NSObject_Disposer.Drain(NSObject ctx) in /Users/builder/azdo/_work/2/s/macios/src/Foundation/NSObject2.cs:line 1072

Given we're never unsubscribing from the KVO legacy observer, what we're doing may lead to issues.

This PR introduces an .xcframework for Apple platforms which could really help in the future also for other purposes.

I moved the observer to a Swift implementation which relies on the new KVO API which lets you skip the unsubscribe and all those managed weak-refs.

Security

For security concerns, the MAUI team should use the attached script to re-build the .xcframework.zip and commit that on my behalf like we did with Android in the past.

image

This is how the project looks like: image

albyrock87 avatar Jul 26 '25 17:07 albyrock87

What were those crashes? Was a bug report filed?

I don't have full proof, but this was the Sentry message in our app:

_isKVOA > Attempt to use unknown class 0x30326ede0. > objc[2293]: Attempt to use unknown class 0x30326ede0.

It wasn't related to CALayerAutosizeObserver though.

But the fact is: CALayerAutosizeObserver is never disposed from C# (or at least it's rarely disposed), because when we disconnect the handler there's nothing removing the sublayer.

So CALayerAutosizeObserver does not leak and does not crash the app only thanks to a combination of things such as:

  • holding a weak ref to a different object readonly WeakReference<CALayer> _layerReference; (not being observed)
  • not having a reference to the observed object (either weak or non-weak)

If we change any of these characteristics it starts crashing. This is why I'm saying this works, but who knows if in the future this may cause crashes with different UIKit versions.

Being able to override the base implementation of CALayer.setBounds on the other hand provides more safety considering we don't have to deal with KVO and weak references at all.

albyrock87 avatar Jul 28 '25 10:07 albyrock87

I've rebased this to net10.0 branch and applied the .xcframework.zip strategy mentioned above in the comments. I think this looks sleek!

albyrock87 avatar Oct 23 '25 17:10 albyrock87

@rolfbjarne thanks for your review, I've applied your suggestions making sure:

  • we use a maui prefix to ensure uniqueness
  • symlinks and metadata are preserved (I'm using ditto because zip --symlinks gave me issues during MAUI build)

albyrock87 avatar Nov 03 '25 12:11 albyrock87

  • I'm using ditto because zip --symlinks gave me issues during MAUI build

Just out of curiosity: what kind of issues?

rolfbjarne avatar Nov 04 '25 09:11 rolfbjarne

  • I'm using ditto because zip --symlinks gave me issues during MAUI build

Just out of curiosity: what kind of issues?

Building the UITests host app was failing during the linker phase saying that it was unable to extract the xcframework zip contents.

I will try again and let you know the precise message.

albyrock87 avatar Nov 04 '25 09:11 albyrock87

  • I'm using ditto because zip --symlinks gave me issues during MAUI build

Just out of curiosity: what kind of issues?

Building the UITests host app was failing during the linker phase saying that it was unable to extract the xcframework zip contents.

I will try again and let you know the precise message.

@rolfbjarne here's the error

12>Xamarin.Shared.targets(155,3): Error  : unzip exited with code 50: Archive:  /Users/albyrock87/github/maui-repo/maui/artifacts/bin/Core/Debug/net10.0-ios26.0/Microsoft.Maui.resources/PlatformInterop.xcframework.zip   inflating: /Users/albyrock87/github/maui-repo/maui/artifacts/obj/Controls.TestCases.HostApp/Debug/net10.0-ios/iossimulator-arm64/Microsoft.Maui.resources/PlatformInterop.xcframework/ios-arm64_x86_64-simulator/PlatformInterop.framework/_CodeSignature/CodeResources     inflating: /Users/albyrock87/github/maui-repo/maui/artifacts/obj/Controls.TestCases.HostApp/Debug/net10.0-ios/iossimulator-arm64/Microsoft.Maui.resources/PlatformInterop.xcframework/ios-arm64_x86_64-simulator/PlatformInterop.framework/_CodeSignature/CodeDirectory     inflating: /Users/albyrock87/github/maui-repo/maui/artifacts/obj/Controls.TestCases.HostApp/Debug/net10.0-ios/iossimulator-arm64/Microsoft.Maui.resources/PlatformInterop.xcframework/ios-arm64_x86_64-simulator/PlatformInterop.framework/_CodeSignature/CodeRequirements-1    extracting: /Users/albyrock87/g...

albyrock87 avatar Nov 04 '25 11:11 albyrock87

unzip exited with code 50

According to man unzip, that's because:

the disk is (or was) full during extraction.

Strange, but in any case ditto works too, so no real problem here I think.

rolfbjarne avatar Nov 04 '25 11:11 rolfbjarne

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 30861

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 30861"

github-actions[bot] avatar Dec 10 '25 10:12 github-actions[bot]

@rolfbjarne I've taken care of your recent feedbacks :)

albyrock87 avatar Dec 10 '25 10:12 albyrock87

/azp run

PureWeen avatar Dec 16 '25 20:12 PureWeen

Azure Pipelines successfully started running 3 pipeline(s).

azure-pipelines[bot] avatar Dec 16 '25 20:12 azure-pipelines[bot]