Private APIs, Objective-C runtime, and Swift

Victor Pavlychko
Sep 8 · 4 min read

Sometimes when building an app, we find ourselves in a situation when we want to use a private API. It may provide some important functionality that is not exposed (yet), or we may want to work around a known platform issue. Or, you may be just debugging your code and poking around to get extra details.

Whatever you are trying to achieve, keep in mind that private APIs are fragile and can change without notice, leaving you with a broken product and frustrated users.

Swift adds another dimension here: restricted APIs marked as NS_SWIFT_UNAVAILABLE. Those are not technically private, but they are considered not safe enough and thus are not exposed in Swift. Calling into such unavailable APIs is pretty ok as they are fully documented and accessible from Objective-C code. This post will focus on accessing a group of Swift-unavailable APIs related to dynamic message dispatch provided by NSMethodSignature, NSInvocation, and NSObject classes. I encourage you to check the documentation if you are not familiar with those.

Nothing is private as long as Objective-C runtime is involved. Things have changed slightly with the introduction of direct methods. Still, a lot of APIs remain accessible if you know how to invoke them.

Invoking private APIs from Swift

But how do you call a method that is not exposed by the SDK? Back in the Objective-C days, everything was easy — thanks to the language’s dynamic nature, you could declare a category with any private methods. They would automatically resolve at runtime.

Swift is a type-safe language, and you no longer allowed to do that. Common approaches rely on Swift-compatible Foundation APIs like perform(_:) for simple functions or low-level method_getImplementation and/or objc_msgSend when dealing with more sophisticated signatures. Unfortunately, this results in complicated, verbose, and error-prone code.

Can we do something better? While the original category-based approach is not available in Swift, we can try something similar — we are still calling into Objective-C.

A cleaner approach

The idea is analogous to the category trick we did previously: we can define an @objc protocol containing methods of interest, use the runtime to add conformance to the class in question retroactively, and then ask Swift to do the typecast. Thanks to the dynamic dispatch used in everything coming from the Objective-C world, the protocol will be implemented automatically by existing class methods we are looking for.

To get started, we will define a protocol for NSMethodSignature matching what we can see in the Objective-C documentation. Notice the @objc(getArgumentTypeAtIndex:) annotation I used here. Normally, the compiler will generate the appropriate selector based on the method name, yet we may want to alter the auto-generated name. Using correct selector names is crucial in our case, where we have to match underlying API signatures perfectly.

Once we have that protocol, we can attach it with explicit objc runtime calls:

Whoa, that’s some boilerplate, and we haven’t added any error checks yet — looks like the right candidate for a helper function. We will fix that right after testing that everything works as expected.

We will be working with method signatures here, and if you are not familiar with those, I recommend reading an excellent article from NSHipster to learn more about type encoding in Objective-C. As a quick recap, I’ll remind that v@: signature stays for “a method that returns void and accepts two implicit parameters: instance reference and method selector.”

As intended, this says, “Number of arguments: 2,” which means that we have successfully constructed a signature for a method accepting two parameters.

Reducing boilerplate

Now back to the class import code. At first, it seems like we can leverage generics in a helper function to remove protocol reference duplication like this:

Except this fails to compile, producing an error: “Static member signature(objCTypes:) cannot be used on protocol metatype NSMethodSignaturePrivate.Protocol.”

You see, NSMethodSignaturePrivate is a protocol, and thus we need a concrete conforming type to create an NSMethodSignaturePrivate.Type value. Because of that, the NSMethodSignaturePrivate.self syntax was repurposed to produce an NSMethodSignaturePrivate.Protocol, which we can use with runtime functions. But that thing does not allow us to call class functions like signature(objCTypes:).

Let’s give it another try:

Now, this looks better, but how do we get NSMethodSignaturePrivate.Protocol from NSMethodSignaturePrivate.Type? I haven’t found any clear way to convert between those two, but we can use the type name as a middle ground here — use Swift reflection API to get the protocol name and then find it’s runtime counterpart with objc_getProtocol:

Conclusion

Objective-C runtime, together with Swift expressiveness, provides a lot of opportunities. We can access Swift-restricted or private APIs using a little hacking, just like we did in Objective-C (all safety measures are on us, though):

Check this gist for a full example: https://gist.github.com/victor-pavlychko/8a896917d8c73f4dded594ab4782214e

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch

Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore

Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store