Back to Blog

Weekly Commit #002

May 10, 20264 min read
... views
WeeklyCommit.NET.NET MAUIiOSAOTLearning

Back for week two. Last time I said I'd report on the .NET 10 migration with hopefully less bad news than expected. Half right.

.NET 10 migration: Android sailed, iOS sank

The migration started exactly the way these things always do — bumped the target frameworks from net9.0 to net10.0, updated the MAUI packages, ran dotnet build, watched the errors scroll. Most were straightforward. Android came together quickly and ran on the emulator without much drama.

Then I tried iOS.

The build succeeded, the app deployed to the simulator and then when I launch it, it crashed immediately. Same code that ran fine on Android. Different platform, different outcome. This was my first real lesson in something that I have never heard about before: AOT vs JIT.

A small detour into AOT and JIT

I didn't know what AOT meant before this week. I've lived my whole development life inside JIT (Just-In-Time). You ship intermediate bytecode (IL in .NET, bytecode in Java), and the runtime translates it to native instructions on the device, on the fly, the first time each method runs. Fast to build, flexible, forgiving.

AOT (Ahead-Of-Time) is the opposite. The compiler turns your code all the way into native machine code at build time. By the time the app reaches the device, there's no IL left to translate.

The reason this matters: Apple doesn't allow JIT on iOS. So every method in an iOS app has to be compiled to native code before it ever ships. Android has no such restriction.

That's why Android worked and iOS didn't. Android was tolerant. iOS demanded everything be AOT-compatible up front, and one of our transitive dependencies didn't AOT cleanly on the .NET 10 iOS workload.

The fix

The real fix was a package bump. Mapsui.Maui had a newer beta, 5.1.0-beta.7, that pulled in Svg.Skia 3.2.1, a version that AOTs correctly on .NET 10 iOS. Updated SkiaSharp to 3.119.1 while I was at it, and the iOS build started cleanly with full AOT, no interpreter hacks needed.

A new problem

With the migration unblocked and AOT working cleanly, I thought I was done. I wasn't.

The app now launches fine on phones running iOS 26.4.x, but on a phone running iOS 26.3.1, it crashes during startup. Not a code crash, a watchdog crash: iOS gives every app around 20 seconds to finish launching, and if the app isn't ready by then, the OS kills it. On simulators running iOS 26.3.1, the app would just be stuck at the loading page since this watchdog doesn't exist in simulators.

The tempting conclusion is "it's an iOS version thing." The honest one is that the startup is just slow, a chain of HTTP calls, secure storage reads, and capability checks running sequentially before the UI can show anything. On fast hardware it fits inside the budget. On anything slower, it doesn't. Older iOS is the symptom; the cause is that we were always close to the limit.

Next week's job: figure out exactly where those seconds are going and start cutting. More on this in #003.

#WeeklyCommit

Follow along here or on LinkedIn where I'll be sharing each new commit.