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
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
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.
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 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
Let’s give it another try:
Now, this looks better, but how do we get
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
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