Skip to content

Instantly share code, notes, and snippets.

@NSExceptional
Last active February 21, 2022 22:40
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save NSExceptional/0c59db523805efcc17ebd1987b67daa1 to your computer and use it in GitHub Desktop.
Save NSExceptional/0c59db523805efcc17ebd1987b67daa1 to your computer and use it in GitHub Desktop.
An example of how to use a private class from Swift. It's not pretty, but it isn't too bad.

Using private Objective-C classes from Swift

In Objective-C, if you want to make use of one of Apple's internal classes, all you have to do is declare the class's interface with the methods you want to use. From there you can create instances of the class by using the NSClassFromString function to obtain a reference to the class object, like so:

[[NSClassFromString(@"_NSSomeClass") alloc] initWithFoo:5]

In Swift, this is not so easy. Referencing the type of the "hollow" Objective-C class interface anywhere in Swift code makes the compiler query the class for type information that is not known at compile time. This presents a huge problem. You cannot dynamically initialize a type either. Though, it seems the following code used to work at one point:

let cls: NSObject.Type = NSClassFromString("_NSSomeClass") as NSObject.Type
let instance = cls()

Of course, Swift now requires an explicit cast using as! and that type objects be initialized with an explicit call to init like this: cls.init(), but it still doesn't compile anymore.

The good news is, there is a workaround. We can make a hollow Objective-C interface to return an instance of the internal class and use it just like we would in Objective-C as long as this hollow class has the same interface methods as the private class we wish to use.


Make a new Objective-C class with the interface of the class you want to use, hereby referred to as the facade class. For the sake of this example, lets say we want to use _UITapticEngine, and that we want to use its actuateFeedback: method. Let's call our facade TapticEngine. Override init in your facade class to return an instance of the desired internal class.

// TapticEngine.h

@interface TapticEngine : NSObject
- (void)actuateFeedback:(NSInteger)type;
@end

// TapticEngine.m

#pragma clang diagnostic ignored "-Wincomplete-implementation" // Silence that warning if you want
@implementation TapticEngine
- (id)init {
    return [NSClassFromString(@"_UITapticEngine") new];
}
@end

In Swift, you can now use the _UITapticEngine class through our new TapticEngine class:

let engine = TapticEngine()
engine.actuateFeedback(3) // phone should vibrate

Our TapticEngine class is never actually initialized, instead forwarding the init call to the internal class, hence the term "facade." Any method calls will work as long as your facade class uses method names and types identical to that of the internal class.

@AriX
Copy link

AriX commented Nov 1, 2016

How about this for initialization?

let tapticEngine = (objc_getClass("_UITapticEngine") as! NSObject).perform(NSSelectorFromString("new")).takeRetainedValue() as! NSObject

This way, you can simply put the _UITapticEngine interface in your bridging header and use it from Swift (just replace as! NSObject with as! _UITapticEngine).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment