JAL JAL - 5 months ago 84
Objective-C Question

How do I expose a private class method of an Objective-C object using protocols in Swift?

Consider two private methods on

UIColor
:


  1. The instance method
    styleString
    which returns the RGB string of the color

  2. The class method
    _systemDestructiveTintColor
    which returns the red color used by destructive buttons.



UIColor.h private header for reference

For instance methods, I can create an
@objc
protocol and use
unsafeBitCast
to expose the private method:

@objc protocol UIColorPrivate {
func styleString() -> UIColor
}

let white = UIColor.whiteColor()
let whitePrivate = unsafeBitCast(white, UIColorPrivate.self)
whitePrivate.styleString() // rgb(255,255,255)


However, I'm not sure how this would work for class methods.

First attempt:

@objc protocol UIColorPrivate {
class func _systemDestructiveTintColor() -> String // Error: Class methods are only allowed within classes
}


Makes sense, I'll change it to
static
:

@objc protocol UIColorPrivate {
static func _systemDestructiveTintColor() -> String
}

let colorClass = UIColor.self
let privateClass = unsafeBitCast(colorClass, UIColorPrivate.self) // EXC_BAD_ACCESS


This causes a crash. Well this is going nowhere fast. I could use a bridging header and just expose the class methods as an
@interface
, but is there a way to expose these private class methods in pure Swift?

I could do this with
performSelector
, but I'd rather expose the method as an interface or protocol:

if UIColor.respondsToSelector("_systemDestructiveTintColor") {
if let red = UIColor.performSelector("_systemDestructiveTintColor").takeUnretainedValue() as? UIColor {
// use the color
}
}

Answer

One way to achieve what you want via protocols is to use a separate protocol for the static method. Static methods in Objective-C are actually instance methods on the metaclass of the class, so you can safely take an approach like below:

@objc protocol UIColorPrivateStatic {
    func _systemDestructiveTintColor() -> UIColor
}

let privateClass = UIColor.self as! UIColorPrivateStatic
privateClass._systemDestructiveTintColor() // UIDeviceRGBColorSpace 1 0.231373 0.188235 1

This will give you both exposure of the private method and usage of protocols, and you get rid of the ugly unsafeBitCast (not that a forced cast would be more beautiful).

Just note that as always if you are working with private API's your code can break at any time if Apple decides to change some of the internals of the class.

Comments