Avoid KVO on CALayer by introducing an Apple PlatformInterop
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.
This is how the project looks like:
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.
I've rebased this to net10.0 branch and applied the .xcframework.zip strategy mentioned above in the comments.
I think this looks sleek!
@rolfbjarne thanks for your review, I've applied your suggestions making sure:
- we use a
mauiprefix to ensure uniqueness - symlinks and metadata are preserved (I'm using
dittobecausezip --symlinksgave me issues during MAUI build)
- I'm using
dittobecausezip --symlinksgave me issues during MAUI build
Just out of curiosity: what kind of issues?
- I'm using
dittobecausezip --symlinksgave me issues during MAUI buildJust 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.
- I'm using
dittobecausezip --symlinksgave me issues during MAUI buildJust 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...
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.
🚀 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"
@rolfbjarne I've taken care of your recent feedbacks :)
/azp run
Azure Pipelines successfully started running 3 pipeline(s).