Swift was always going to be part of the OS (2022)
belkadan.com
belkadan.com
Maybe Apple is more aggressive in adopting Swift as their operating system language, because Microsoft always has this split that the Windows division was C++/COM and .NET did not really fit into their world.
Apple has explicitly set out to adopt Swift as a successor language to C, Objective-C, C++, and Objective-C++[0][1]. This stands in stark contrast to Microsoft's vision for the CLR, which was… to be a better Java, more or less? (Does anyone actually know what the .NET initiative was all about? Microsoft went absolutely ham on it in their branding, but the momentum unceremoniously fizzled out before Server 2003 went RTM.)
That said, apropos .NET fitting into Windows, the work on Singularity[2] by MSR had a lasting impact. I'm told that there is in use a System C# dialect that generates native object code via a more production grade version of Bartok[3].
0: https://youtu.be/lgivCGdmFrw
1: https://developer.apple.com/swift/#:~:text=Swift%20is%20a%20....
2: https://www.microsoft.com/en-us/research/project/singularity...
Technically the ".NET initiative" was all about web services. But not the modern kind, it was about SOAP. And I guess also Code Access Security-- a way to run untrusted (and even remote) code securely. Ahem, "securely".
The .NET Framework, CIL, and CLI/CLR and it's initial languages, C# and VB.NET, were in support of those technologies, but ended up being the only parts with real staying power.
I am a big fan of the .NET Framework (and it's evolution in .NET Core / .NET 5)-- so many rough edges of the Java runtime concepts and Java language concepts were polished, and a lot of new innovations were introduced (proper annotations, a framework for handling code isolation, a better reflection API, real runtime generics in .NET 2.0 and more).
There's definitely things they tried to improve on that... weren't really improvements. The way "assemblies" are matched in .NET is much more sophisticated- the goal there was to try to kill DLL hell. It evolved into the Global Assembly Cache, which is sort of the Windows Registry of DLLs. Not a huge fan of those bits.
> That said, apropos .NET fitting into Windows, the work on Singularity[2] by MSR had a lasting impact
Firstly, of course C# is used quite a lot within Windows in the user space.
I worked on some open source stuff around kernel level C# that was inspired by MSR's work here-- Singularity still used a very small core written in C++ iirc, we were interested in reducing the amount of unmanaged support code as much as possible as well as replacing the unmanaged c++ bits with unmanaged c#. That work grew into SharpOS. There was also a competing project called CosmOS. Lost to time now.
[0]: https://joeduffyblog.com/2015/11/03/blogging-about-midori/
The Global Assembly Cache did not make the jump to the modern .NET (Core). There was the thing called `dotnet store`, but it’s broken since .NET 6: https://github.com/dotnet/sdk/issues/24752
The assembly redirection hell has also been greatly reduced there.
https://devblogs.microsoft.com/dotnet/net-core-updates-comin...
Also why since day one, it was polyglot, COM was part of the stack (as next step from VB 6 and MFC/ATL), and Managed Extensions for C++ were included (later replaced by C++/CLI in .NET 2.0).
The problem is that Microsoft isn't Apple, and WinDev couldn't care less about this, and stayed true to their COM/C++ tooling, similiarly when the Longhorn effort failed (mostly due to sabotage from Office/WinDev teams), Vista redid most of the .NET based ideas into COM/C++, and since Vista COM has been the main delivery mechanism for new Windows APIs (WinRT is yet another take on COM).
Bartok was used on the Windows Store compiler for Windows 8.x, via MDIL linker.
.NET Native on Windows 10, grew out of Project N, which was influenced by System C# used in Midori, not Singularity.
Language bindings could then directly go through the C API instead of having to go through an ObjC- or Swift-shim, or calling directly into the brittle ObjC runtime functions (is there even something similar to the ObjC runtime for Swift?, e.g. a C API to create Swift objects and call methods on them?).
You can also see an example of what a .NET integration with Swift ABI looks like here: https://github.com/dotnet/designs/blob/main/proposed/swift-i...
That's my main gripe with apple's approach to swift and swiftui. Yes, tech is getting better every day, but unless you target latest OS version, you can't use new fresh stuff until it's on enough devices around you. And that pretty much guarantees that no matter what apple adds, you still have to wait a year or two until you can safely start using it.
In modern android, kotlin(and compose) also part of the system, yet all the apps do not rely on the system libs, but rather inject the latest available runtime with each of the apps. It takes more space, but instead allows developers to target latest available stack, no matter what core os this app is being run on.
Now that the developers on the core part don't need to spend time on compatibility -- or, just dont want have to make the base choice of being a runtime dependency -- they can spend time on other things instead.
This seems like a net negative at a glance, on the surface it means the apps are less compatible, so the second level is forced onto the older iterations, in practice, since each iteration has to worry about a lot less, the older iterations are _also_ a lot better instead.
It is of no surprise to me these Apple or Apple-like systems tend to be better overall, as opposed to the other philosophy of Android.
It leaks into all the levels. In the Java app, it is usual to see a deprecated warning that keeps working and it is maintained, and someone pays for that. The negative side is that there's no reason to get rid of the said dependency, either.
My point is that lowering the maintenance cost of _any_ app or systems in general, leaves room for improvement in all the other areas, as long as you don't fall behind -- if you are allowed to fall behind, you can afford to, if not, the end result is better given enough time.
This might have been true in the past, but it's been getting worse over the last decade.
For instance: The new parts in macOS that are written in Swift seem to be mostly inferior to the parts they replaced (see for instance the new settings window written in SwiftUI, which UX wise is a joke compared to the old one, even though the old settings windows wasn't all that great either - case in point: try adding two DNS servers, searching for 'DNS server' only allows adding one item, then the DNS server panel closes and cannot be opened again without repeating the entire search, no idea how this mess made it through QA).
If Swift is so much better than ObjC, then we should start seeing improvements as users, but that doesn't seem to happen, instead things are getting worse in new OS versions. Why is that?
But yeah, in the end, software quality needs to be tackled on the organizational level.
Just at the moment that Swift and, later, SwiftUI get introduced, and entirely coincidentally, management breaks?
We had this in hardware, with machines getting worse and worse and Apple getting more and more arrogant about how perfect they were. In hardware, they had their "Come to Jesus" moment, got rid of Jonathan Ive (who had done great things for Apple, but seemed to be getting high on his own fumes), pragmatically fixed what was wrong and did the ARM transition.
With software, they are still high on their own fumes. The software is getting worse and worse at every level, and they keep telling us and apparently themselves how much better it is getting all the time. Completely delusional.
Swift is a programming language. SwiftUI is a UI framework. The programming language doesn’t dictate UX. The new Settings application doesn’t have worse UX because of its programming language.
> If Swift is so much better than ObjC, then we should start seeing improvements as users, but that doesn't seem to happen, instead things are getting worse in new OS versions. Why is that?
Because Apple are institutionally incapable of writing software at a sustainable pace and things gradually get worse and worse until somebody high up enough at Apple gets fed up and halts development to catch up with all the quality issues. This isn’t anything new to Swift; they took two years off from feature development to release Snow Leopard with “zero new features” because things had gotten too bad, which happened years before Swift. They are just far enough along in the current cycle that these problems are mounting up again.
> The tradeoff is that the app runs faster, looks better, works better.
I haven't noticed any of that so far in new macOS versions, and it is indeed not something where the programming language should matter at all.
Swift the language strongly informed SwiftUI, which in turn strongly informed the applications written in it. The path of least resistance defines the most likely implementation. If I have to go the extra mile to do something, I probably will not, so worse UX (by some metric) is a direct consequence of that constraint.
Jetpack Compose is a similar API on Android, except the implementation is good, so apps using it are good.
This is not an accurate characterization of Snow Leopard. See "The myth and reality of Mac OS X Snow Leopard": https://lapcatsoftware.com/articles/2023/11/5.html See also: https://en.wikipedia.org/wiki/Mac_OS_X_Snow_Leopard
There were many significant changes to the underlying technologies of Snow Leopard. Moreover, Snow Leopard was not, despite the common misconception, a "bug fix release". Mac OS X 10.6.0 was vastly buggier than Mac OS X 10.5.8.
I assume that Apple does not have traditional QA that tries to break flows that aren’t the new hotness. The amount of random UX breakage of boring old OS feature is quite large. Or maybe Apple doesn’t have a team empowered to fix the bugs?
To be somewhat fair to Apple, at least they try to keep settings unified. Windows is an utter mess.
Good idea, doesn’t work.
Hasn't it always been this way with native desktop applications using the OS vendor's UI toolkit? What I hear some folks describe as "the old days" of Windows, Mac, etc.
I think there also always was a new OS version for the new hardware, though, with the new hardware not running at all on older versions.
The high speed at which computer tech improved at the time made that a bit of a smaller issue.
Apple had a mechanism (ROvr resources) to allow the system software on disk to override components from ROM.
The only exception is security.
An unmaintained app that solves a specific problem is never going to break by itself “in a not distant future”. Only if its environment changes so much that it cannot be run anymore (including security fixes missing in the app) does this happen.
I recall a firm making good money while using their internally custom-built software for MS DOS with no way to change it whatsoever -- the software firm that wrote it was probably already long out of business, source code was not available -- and that was in 2019 which was already long past the days of MS DOS.
I think it is worthwhile for OSes and other core software infrastructure to support running even unmaintained apps as much as possible because it reduces the need to rewrite (or overhaul) programs only due to a lack of maintainance for the existent one.
Not caring about this is IMHO accepting to waste a huge portion of the advantages that software gives us compared to other technology (software by itself never breaks from physical defects or continuous use, does not stain, etc.).
I'm not sure that's right. The developer does a ton -- it just feels automatic to you. All your SwiftUI apps are not going to use Metal shaders automatically now just because they're now so neatly supported in iOS 17. And every update to Swift/SwiftUI comes with deprecations and those need to be addressed (sooner rather than later), including adding #ifavailable macros to cover all the bases.
The real benefit is that developers can add new functionality or write simpler code than they would have to before. I could be wrong, but the stuff you get "automatically" is minimal IMHO. It's just that developers start targeting the newest iOS version during beta so that by the time users upgrade, apps can be ready
Sure, as a dev, there’s plenty to do and test and update, but there’s also heaps I get for free, especially when following Apple’s “best practices.”
A simple example was a year ago when Apple tweaked some UI elements to look sleeker and changed default styles (e.g., lists, pickers, etc.).
My SwiftUI app and other SwiftUI apps compiled against the then-current iOS SDK immediately showed the new UI elements when run on the latest iOS beta. Didn’t even require me to recompile.
Stuff like that is elegant and can only be done when the libraries are included in the OS.
The flip side is, of course, that if you, for some reason, have a particular thing in mind and you don’t take precautions to lock it down in your code, it’ll stop looking that way when changes are made in the next iOS.
But I guess that’s what beta testing is for, and I’ve yet to come across a freebie that I didn’t like, but I’ll concede that it’ll depend from dev to dev.
They control the OS. They also control Swift. So why not embed the Swift version that the app was built against, and then download that swift version to the user's device when they download an app that uses it. Then:
- The app runs against the exact Swift version it was built against
- The app size is smaller because you're no longer shipping the Swift libraries with the app
- The impact on the user's device space is minimized since it only downloads each version of Swift once (to be shared by all apps that use that version), and only on demand.
- If a new version of Swift comes out before an OS upgrade, simply add it to the list. It gets downloaded the same as the rest.
They could even add some predictive Swift downloading for the most popular versions of Swift to avoid unnecessary delays downloading it.
Apple only needs to do this because they are charging a pornographic premium for storage.
Apple is in the device business. They make most of their revenue by selling you a new device.
A 10 year old Mac is basically a brick unless you want to mess with OpenCore Legacy.
The solution to this problem is to target a newer OS like Apple wants you to. Their users are going to buy a new system anyway, they’re the most affluent segment of the PC market.
I thought Apple users were more likely to buy/own used devices than PC users were? I'm not fully caught-up on the statistics, but I'd assume that's still true.
I’ve got a 2012 Mac mini and it’s limping along with the OpenCore Legacy patcher. I got the kernel panics to stop but they came back with a recent update. I’m gonna sell the thing and probably switch those duties to a Linux server.
In any case, maybe you can add heaps of complexity to core OS things and save some disk space; but you still need the full patched dynamic library in memory when the process is running, so at the very least you'll end up with bloat from lots of versions of the dynamic libraries loaded in memory when processes with different versions are running...
Maybe you could tackle both of the problems by storing a base version of the dylibs and then have other dylibs which provide replacements for only the symbols which have changed between versions... but this would severely limit the kind of thing you can do without just basically having to override all symbols. And automating this process would be hard, since compiler upgrades could cause small code gen changes to a bunch of symbols whose behavior haven't changed and you wouldn't want to ship overrides for those.
In the end, while I'm sure there are things you could do to make this work (Apple has some talented engineers), I also understand why they wouldn't.
I don't think there's something else in the "general purpose languages with substantial real-world use" camp that touches on those 3 points quite like Swift does.
On the other hand, non-Apple developers have good reason to avoid Apple, due to the extreme anti-competitive behavior. It's the C# story all over again.
At least C# has a real cross platform story for a while now.
Yes, there is VS Code, which besides being Electron based, Microsoft is quite open that will never achieve feature parity with VS.
If there's a deep dive on this, I'd love to read it. Could one of the possible reasons be targeting few-core systems and providing as deterministic memory usage as possible to fit into RAM on iOS devices without putting the burden on the programmer?
For the typical application though, it's fast enough, you get same order of magnitude performance as C++ or Rust with a almost Python like mental load. As the success of other dog-slow languages show, this is a major selling point.
Perhaps aggressive PGO could help to close some of the gap. The problem is that PGO requires effort on the part of developers to write comprehensive test cases and it's not clear how to scale that workflow. Large companies can write representative test cases and scale PGO on their performance-sensitive services, but your average iOS app developer won't be willing to do that.
HotSpot C2 and .NET Dynamic PGO-optimized compilations first and foremost help to devirtualize heavy abstractions and inline methods that are unprofitable to inline unconditionally under JIT constraints, with C2 probably doing more heavy lifting because JVM defaults to virtual calls and .NET defaults to non-virtual.
With that said, I am not aware of any comprehensive benchmarking suites that would explore in-depth differences between these languages/platforms for writing a sample yet complex application and my feedback stems mostly from microbenchmark-ish workloads e.g. [0][1].
Performance aside, I do want to compliment Swift for being a pleasant language to program in if you have C# and Rust experience.
[0] https://github.com/ixy-languages/ixy-languages (2019)
[1] https://github.com/jinyus/related_post_gen (2023)
I also like Swift, if anything it helped to bring back the pressure that AOT compilation also matters.
Whereas Windows Phone would use Windows Store to AOT compile the application, Android would AOT on device.
Thus initially, it felt like using the tiny phones for that would be a bad decision, and it was, as the JIT was reintroduced 2 versions later (Android 7).
However, it was a mix and match of all modes, interpreter hand written in Assembly for quick startup, a JIT with PGO data gathering, AOT compiler with feedback loop from PGO data, latter on, sharing PGO data across devices via Play Store services.
This mix of JIT/AOT with PGO sharing across everyone, brings the optimal execution flow that a given application will ever get, allows reflection and dynamic loading to still be supported, and AOT compiler toolchain can have all time of the world to compile on the background.
I’m not saying that it is a bad choice, it is probably a good one in case of battery-powered machines with small RAMs, but I think tracing GCs get a bad look for no good reason.
But you are right about memory usage and battery ... this is why iOS devices require less memory than Android devices for comparable performance (or better performance in some cases).
Apple's frameworks used GC before, and they switched to ARC.
Moreover, ARC itself indeed modifies refcounts atomically. If it didn't, you would not be able to reliably determine the liveness of an object shared between threads. Now ask yourself whether atomically updating dozens of integers is faster than flipping a bit across a table of pointers.
Like in many things where "Apple did it first".
[1] Figure 3 in https://iacoma.cs.uiuc.edu/iacoma-papers/pact18.pdf
---
Additionally, people comparing ARC to Objective-C's conservative GC in the replies don't seem to understand that (1) refcounting is a form of GC, often times inefficient compared to a mark-and-sweep GC, and (2) conservative GCs are quite limited and Apple's implementation was pretty bad compared to other implementations.
Objective-C objects are basically all a struct objc_class* under the hood, and conservative GCs in general cannot distinguish whether a given word is a pointer. Even worse, for a conservative GC to determine whether a word points into a heap-allocated block, it has to perform a lengthy, expensive scan of the entire heap. It also doesn't help that Apple decided to kickstart the GC if your messages began with "re" (the prefix for "retain" and "release" messages, which were used all the time before ARC came around). So at one point in time, you were able to marginally boost performance of a garbage collected Objective-C application by avoiding messages beginning with "re"!
However it also means that if all apps have to target the latest OS just to get some UI features, they will quickly end up dropping support for older hardware as well.
If you want a good example of this, check out the download page for Calibre, a simple app that helps to manage and convert eBooks.
like javascript in the browser...
My only regret was not using SwiftData (since it was only released with IOS 17), CoreData is not that nice.
Luckily SwiftData is really easy to pick up and because it’s build on top of CoreData it’s pretty easy to convert it over to SwiftData.
How is an app built on Swift 6 runnable on an OS with just Swift 5 runtime? I would have thought developers have to target the minimal Swift (and iOS version) at build time and live with the features available then.
Swift 6 will not change the ABI in an incompatible way. If Swift 6 introduces features that require runtime support, then code that uses those features will not back-deploy, but other code will. We have seen this before. For example, Swift 5.7 implemented SE-0309 (“Unlock existentials for all protocols”). Some of the new features of SE-0309 required runtime support, and if you wrote code that used those features, the program would not back-deploy. But (I think) the compiler emits an error if your deployment target doesn't ship with the Swift runtime that supports those features.
The big changes in Swift 6 will be new semantic restrictions related to concurrency, and possibly a small amount of breaking changes to syntax. These changes will prevent some existing Swift 5 code from compiling in Swift 6 mode. That is the reason the major version number will change.
Why?
It only works for functions/methods, but it will allow devs to use new APIs sooner!
[1]: https://www.hackingwithswift.com/swift/5.8/function-back-dep...
And I don't think it is. But COM itself was such a pain to deal with. Notably all of my time with it was spent from within .NET so maybe it was less painful in C++-- but .NET was at least partially designed to work well with COM. Insert crying emoji.
(Of course I think Microsoft intended us to stop using COM and instead use SOAP, but that didn't happen)
DCOM, nowadays out of fashion also uses processes, and in case of exposing In-Proc COM to the network, a wrapper server process is required.