This is basically how the Cocoa-Java bridge worked 25 years ago, except of course Java has GC.
The article makes no mention of memory management. ARC is a compiler feature, so I guess this C code makes a ton of calls to an nsobject_release() function for everything allocated in Obj-C world. The code example shown seems to be just leaking the NSString instance.
Which platform doesn't have Clang? Why not allow the Obj-C compiler to take away all that C boilerplate and fragile retain counting gruntwork, and then wrap those calls in a concise C API that provides the exact interface your application needs? I don't really get it.
> Why not allow the Obj-C compiler to take away all that C boilerplate and fragile retain counting gruntwork, and then wrap those calls in a concise C API that provides the exact interface your application needs? I don't really get it.
100%. If you’re tying your codebase to Cocoa (or other objc framework), it makes zero sense to avoid Objective-C while also requiring intimate knowledge of Objective-C conventions.
Plus, you’re potentially breaking all sorts of related tooling. Documentation lookup becomes harder. Will Instruments work right? Can you get clang Static Analyzer tips? What about objc-specific compiler warnings? Any time you want to search for code / examples / whatever, you have to translate the symbols. What does it do to a stack trace? Do you get the same deprecation warnings? Does it work correctly if a team member uses a different SDK version, either intentionally or accidentally?
This way you dont have to wrap by hand, and any time Apple releases a new API or makes a change you push a button and have a new wraper. Writing a wrapper also requires someone who knows Objective C.
There are obviously things that could be refined, but for a first release it has proven to be very effective in production.
But using the raw Cocoa Objective-C APIs via a wrapper... also requires someone who knows Objective-C? Because how else are you going to understand the API documentation?
It seems that if you unleash this wrapper onto a C programmer who has never used Cocoa, you'll just be in a world of hurt with unexplained memory leaks and segfaults due to misunderstood object lifetimes.
For example, class factory methods like +[NSString stringWithFormat:] will return an autoreleased instance. How will the C programmer know this convention if they've never used Objective-C?
Practically nobody starts a new project on macOS / iOS that uses manual memory management instead of ARC, so you won't have a lot of documentation when you go down this path. The Apple documentation for the aforementioned stringWithFormat: method doesn't mention (anymore!) that the return value is autoreleased because that doesn't matter with ARC, but makes all the difference for someone using the C wrapper.
The API documentation is available in the header files. If you were to parse those, I'm pretty sure you could generate the docs for the C API automatically, just as Apple does for Swift. You'd need to do a fair bit of search-and-replace to get the function and type names right, but the API surface is finite and it doesn't change that often. If you were willing to put a good amount of work into a generator, you could get it to a state where it would be able to fully parse all of the Apple SDKs.
There's no magic in ARC, it's all just calls to retain and release that the compiler synthesizes for you automatically. If you have the headers, you can do what it does and figure out which methods return objects that need to be manually released. If you were generating bindings for a language with RAII, like Rust or C++, you could create a bunch of wrapper types and give them destructors, the best you can do for C is adding an extra paragraph in your docs.
>It seems that if you unleash this wrapper onto a C programmer who has never used Cocoa, you'll just be in a world of hurt with unexplained memory leaks and segfaults due to misunderstood object lifetimes.
This is true for every API under the sun. If you just use it without reading the documentation or understanding the design philosophy, you're alyways in a world of hurt.
The same things that are true for Obj-C are true for the wrapper, it just lets you use the API in C. No more, no less.
That’s the point. To use this wrapper correctly, you need to know how Objective-C works and you need to be able to read Obj-C code to understand the documentation.
In fact you need to know quite a bit more about Obj-C details than the average iOS programmer because you’re doing manual memory management rather than compiler-assisted ARC (automatic reference counting).
So I don’t really see who this is for. The person who has to learn Obj-C but must pretend not to know it to please the boss who insisted on plain C?
> If you just use it without reading the documentation or understanding the design philosophy
They literally covered this point for you:
> The Apple documentation for the aforementioned stringWithFormat: method doesn't mention (anymore!) that the return value is autoreleased because that doesn't matter with ARC
It is no secret that documentation surrounding the "old ways" of Apple dev is a pain in the ass to find.
It is not documented on a per method basis. You can see by looking at the name of the method that it does return an auto released instance… this convention is well documented and made ARC possible.
The point is: what's the point of wrapping this at all? You can just directly call `[object foo]` instead of doing your own `object_foo()` wrapper just because you want to make the syntax prettier. It's like doing `#define saith printf` -- you're obfuscating your platform-specific code and making it more difficult for people familiar with the platform to understand what's going on. This is subtracting value from your sourcebase, not adding value!
And it doesn't help people who aren't used to Objective-C, because they'll be utterly, hopelessly lost trying to deal with platform-specific features like autorelease pools and Foundation-specific calling conventions. And they won't be able to find docs for your weird syntax.
You cannot remove the platform from the platform specific bits! It will come back to bite you.
Also, you really need to check the memory management situation -- the code samples are leaking memory out the wazoo and it doesn't give confidence that any code using your wrapper isn't doing the same.
When you’re using C, you will have to take care of the retain/release cycle if you allocate by hand objects. The internal of the object should take care of themselves though.
It’s a bit like using unsafe in rust - you’re the one responsible.
I don’t see much of a point of doing it that way. I do prefer working with ObjC with some C in it if need be ;)
I think the Oracle v. Google lawsuit (regarding Android’s use of Java) validated Jobs’s intuition that it wasn’t a great idea to have somebody else’s proprietary programming language as a first-party API.
If Java had become the primary language for Cocoa, the path to Cocoa Touch and an iPhone App Store could have been much more complicated.
Java bridge only existed because at first Apple wasn't sure if the Object Pascal/C++ developer community would embrace Objective-C.
When most of them rushed to adopt Objective-C, there was no reason to keep two stacks around, specially when they were still in the red, bleeding money.
Oh there were rough edges but mostly because they abandoned them quickly but being able to use a great IDE to write code and have it all autocomplete was way better than having xcode open and the docs trying to figure out the APIs was painful.
Autorelease only works if you put the object in the autorelease pool. Some calling conventions also put it in the pool for you but if all you did was manage pools you'd leak memory.
You can't rely solely on autorelease unless you're going to put literally everything in the autorelease pools, which would not be great for performance.
Those who don't understand Objective-C are bound to reinvent it...badly.
"... you could even use the generated API with Lua, Python, Ruby or JavaScript"
Guess what, all these have Objective-C bridges, usually multiple ones, because the combination of being just a thin shim on top of C as well as that thin shim being essentially a Smalltalk with full dynamic messaging and runtime introspection + intercession means interop is Objective-C's bread and butter.
No C code generation required.
This should be no surprise because that was exactly the stated goal of Objective-C, and they achieved it admirably. If you think Objective-C is a closed, vendor-specific language like Swift, you truly don't understand it. At all.
Someone once described Objective-C as "COM with language support" and that's pretty accurate.
> Guess what, all these have Objective-C bridges, usually multiple ones, because the combination of being just a thin shim on top of C as well as that thin shim being essentially a Smalltalk with full dynamic messaging and runtime introspection + intercession means interop is Objective-C's bread and butter.
Exactly.
In fact sometimes in lieu of shell scripts I write simple utility JavaScript files using Objective-C APIs and run them under osascript.
I couldn't write it as a shell script because I wanted to get the "date added" rather than the "date modified" or "date created" but this is available in Cocoa APIs.
This is also what the "official" C++ API for Metal does (https://developer.apple.com/metal/cpp/), it's an automatically generated bindings wrapper which calls into ObjC runtime functions without an ObjC shim inbetween).
Bit of a shame that this idea isn't extended to all system frameworks and not just C++ but also C bindings (official C bindings would make life a lot easier for all other programming languages).
I also dabbled a bit with this idea by parsing clang AST-dumps of macOS system headers and generate C binding functions:
As mentioned on the Swift talk at CppNow 2023, most of the C bindings are actually C++ with extern "C", and Apple would rather write less of them than more of.
"CppNow 2023:Introducing a Memory-Safe Successor Language in Large C++ Code Bases"
This may or may not apply to OPs app, but for mobile-first apps with a broad audience, having an identical appearance/interaction model across competing mobile platforms is a bug, not a feature.
Or maybe not exactly a "bug" but at least an unfortunate tradeoff because your org lacks in-house expertise on one or more of the target platforms.
The case of a single person using many apps on a single mobile platform is just so much more common than the case of a single person using your app on multiple mobile platforms that it makes far more sense to hew to the platform idioms at the cost of cross-platform consistency.
An obvious exception might be something like Slack, where it's common to use both the mobile app and the web app (either directly or via an Electron-like wrapper), but calling Slack "mobile first" is also a stretch.
I'm surprised the authors didn't go for parsing the headers that Apple ships with their SDKs instead of extracting method and class names from the runtime.
That would solve many of their issues, including missing argument names, type information for arguments and return values and access to iOS frameworks on Mac.
I'm not sure how difficult that would be to accomplish, though, considering how macro-heavy C often is.
We have considered parsing both headers and documentation in order to make the generated API prettier. This is the first release, so we want to see if there is interest, but yes there are lots of things we could do to expand the project (Auto documentation in header files, other language bindings...)
No its not. All Objective C objects can be assessed using a C API. This C api is very hard to use, so tecnically its nicer C code wrapping a bad C API.
I wonder how they deal with things like creating user interfaces (and the outlet/action pattern), passing blocks, exceptions or creating their own classes, something that you have to do quite frequently because of Apple's heavy use of the delegate pattern. The runtime does provide class creation functionality, so it's very much doable in C, but it requires a lot more code than doing it the official way. Creating classes in this manner is also an imperative action, so you need to keep track of whether your class has already been registered and return the existing instance if necessary.
You should not use exceptions in ObjC unless it's truly exceptional; most system code (and all ARC code by default) is not exception-safe and will leak objects if thrown through. And Swift will insta-crash if it sees one.
Interesting project, though I'd rather just use Obj-C. I guess if you wanna use it in something like JavaScript or Lua it'd make sense to have C as a bindi g layer.
SwiftUI related things might be the only swift-only API apple is actively working on as far as I know. If there's another one I don't remember it. Almost everything they release will be accessible in obj-c.
(It's not a big blocker to still be tied to Objective-C IMO; I'm mostly just responding to the "might be the only swift-only API apple is actively working on" line.)
Nearly all "new" frameworks have been Swift-only over the last few years. Like you mention, CryptoKit and widgets, but also Combine, the entirety of SwiftUI, and even smaller things like the ProximityReader framework they released with iOS 15.4. They're even rewriting Foundation using Swift while exposing an Obj-C compatible ABI. Of course, any new API in frameworks which are already Obj-C are usually written in Obj-C and exposed to Swift. This means new Obj-C code will be written for years to come.
As far as I'm aware only visionOS has some Swift-only frameworks. All the actually important frameworks for macOS and iOS development are still callable from ObjC.
There’s a whole bunch of frameworks that are Swift only like SwiftUI, WidgetKit, SwiftData, MusicKit, SharePlay, DeviceActivity and others and can’t be called from ObjC
But you can write Swift code that can be called from objc, right? Could you write a wrapper/shim in Swift for "object c" that you'd then call using the method outlined in the article?
> Swift code that can be called from objc, right? Could you write a wrapper/shim in Swift for "object c"
you could, but there are some things that are harder to represent such as structs and complex enums (since everything "objective" in obj-c is a reference), or even async/await based api's (perhaps with lots of black-based wrappers?)
I'm not sure I understand the advantage here other than avoiding a few `.m` files?
The maintainers will still need to know ObjC to understand the Cocoa calls, and this presumably removes a lot of the ObjC niceties in the process.
And now you have an extra layer to maintain, as well as resource allocations to maintain, that ARC would take care for you. Is all the overhead worth it, over just learning to put a few square brackets around the place?
The blog does mention stuff like bindings to other languages, but since ObjC is just C, that's no easier or harder really than before.
This just like the JSPatch which creates a JS wrapper use Cocoa API in javascript environment.
I think to make the code compile and run for multiple platform, the top level API is the key, not the some-kind-of Objc-based-c-runtime.
Flutter is a good example. Chromium is a better example.
What this shown is more like the failing C# for Apple. I have no idea why so many devs used this, but with the end of Visual Studio for macOS, no more C# for Apple platform.
> but with the end of Visual Studio for macOS, no more C# for Apple platform.
On the contrary!
.NET has been cross-platform for years now and runs fine in Linux, Windows and macOS.
Visual Studio for Mac was a legacy piece of software and was promptly replaced by either Jetbrains Rider or VSCode + Microsoft's C# Dev Kit.
If anything, this transition will foster C# in macOS since newbie devs wont stumble upon by accident on the horrible experience that the legacy IDE had to offer.
While I don't disagree with your sentiment, that is not a reasonable expectation for anyone intending to develop for Apple platforms. Apple has made it quite crystal clear for a long time now that Swift, and only Swift, is the One True Way going forward.
Its absolutely crazy to me that both major mobile platforms don't have APIs that are available with bindings for all major languages. Forcing developers to use Java on android (A VM on a low powered platform), and ObjectiveC/Swift(Languages that are barely used on other platforms) is so hostile to developers.
I mean, hang on there... Objective-C, while probably frustrating to get started with, is literally "just about C". Projects have been doing Objective-C interop for decades now, very similar to what is being done in the posted article. It's not really gated off at a programming layer, moreso in how Apple's been so frustrating about developing on non-Apple hardware.
It's not exactly what I would call "hostile", language-wise. Swift, on the other hand, might be a bigger barrier...
Yeah, bindings for UIKit aren’t actually all that challenging to write. There’s quite a number of UIKit apps written at least partially in languages other than Obj-C/Swift.
Android Framework on the other hand is not great in this department. Practically speaking it’s just Java and Kotlin. I wish I could write Swift there because even if Kotlin is similar syntactically the JVM baggage can be irritating.
Complaining about the JVM on Android ("a low powered platform") feels like a take that's about 10-15 years out of date. A modern Android phone is several orders of magnitude more powerful, however you might want to measure that, than the machines that Java was first designed for. And anyway, Android doesn't really use a VM anymore - it's been AOT compiling apps on installation for around a decade.
There's about a billion things about developing on Android that are absolutely miserable. HLL-induced perf bottlenecks aren't usually one of them.
I don't think GP's complaint is about Java and Swift being unperformant, I think the complaint is that you can't use the language of your choice easily. If we just had a C API we could interpret Ruby or Python with a standard interpreter. But instead, if you want to write a Ruby or Python mobile app, you need to translate objects between the language of your choice and Java/ObjC.
Right -- Android does have a C API but it’s a pain to use and really incomplete: https://developer.android.com/ndk/guides/stable_apis If you want to do anything else you need JNI which is an even bigger pain.
I don’t really understand why the Android team haven’t made it easy to call any Java API from C, with auto-generated bindings or some such.
I think they just don’t believe in apps using native code, beyond the special case of games (note that pretty much all the native APIs are to do with low-level graphics and sound).
You don't need to wonder, it is quite explicity mentioned on the NDK documentation.
"The Android NDK is a toolset that lets you implement parts of your app in native code, using languages such as C and C++. For certain types of apps, this can help you reuse code libraries written in those languages."
"The NDK may not be appropriate for most novice Android programmers who need to use only Java code and framework APIs to develop their apps. However, the NDK can be useful for cases in which you need to do one or more of the following:
- Squeeze extra performance out of a device to achieve low latency or run computationally intensive applications, such as games or physics simulations.
- Reuse your own or other developers' C or C++ libraries.
"Swift is a successor to the C, C++, and Objective-C languages. It includes low-level primitives such as types, flow control, and operators. It also provides object-oriented features such as classes, protocols, and generics, giving Cocoa and Cocoa Touch developers the performance and power they demand."
The only point I’m trying to make is that C and C++ are well supported on iOS -- they work out of the box with the standard dev tools and there are plenty of Unix system libraries (e.g. libcurl). On Android, C and C++ are very much second class citizens. I find that a strange decision.
How many Android devices are in service in the world? Whats the total power consumption of all Android devices? Shaving just 1% is probably a couple of coalfired power plants worth of CO2.
Objective-C and Swift are used all over the Apple platforms, including desktop macOS. So „barely used“ on other platforms is not really true.
Also considering the number of iOS devices, those two languages are probably more usable in practice than for example C#, even if the latter is cross-platform.
"barely used on other platforms" clearly meant (to me anyway) barely used on non-Apple platforms. They're not terrible languages, but they're pretty uncommon outside the Apple ecosystem.
The interests of platform vendors and application developers are not aligned.
Platform vendors seek to lock in developers and discourage development on competing platforms, while developers wish to leverage their skills portably across multiple platforms.
Google isn't capable of aligning itself with its own mission, let alone locking in developers. They'll sooner release 10 programming platforms invented for 10 new messaging apps.
Given that Swift is open source, I'm surprised Android hasn't made it a 1st party alternative to using Kotlin.
They could capture alot more developers this way, as learning 1 language for both mobile platforms would facilitate more iOS only apps (there are many still even today) to get ported to Android.
This may finally make Android tablets viable, as the app ecosystem for tablets isn't as good as iPadOS
Android has 70% of the world market, there is no reason to care about Swift.
Also Kotlin and Android Studio are mostly developed by JetBrains that is in bed with Google for Android tooling, if anything I expect more Google acquiring JetBrains than supporting Swift on Android.
Objective-C has been open source for even longer, and Cocoa and the associated mobile toolkits eminently clonable. I'm rather more surprised that Google didn't make an 80/20 iOS compatibility toolkit 15 years ago. If they didn't do that then when iOS was dominating the hearts and minds of pretty much every developer behind any noteworthy mobile app, then I wouldn't expect them to make Swift a first-class priority today, when Android has basically "won" on the global scale.
The article makes no mention of memory management. ARC is a compiler feature, so I guess this C code makes a ton of calls to an nsobject_release() function for everything allocated in Obj-C world. The code example shown seems to be just leaking the NSString instance.
Which platform doesn't have Clang? Why not allow the Obj-C compiler to take away all that C boilerplate and fragile retain counting gruntwork, and then wrap those calls in a concise C API that provides the exact interface your application needs? I don't really get it.