Skip to content

Instantly share code, notes, and snippets.

@victor-pavlychko
Created September 7, 2020 14:42
Show Gist options
  • Save victor-pavlychko/8a896917d8c73f4dded594ab4782214e to your computer and use it in GitHub Desktop.
Save victor-pavlychko/8a896917d8c73f4dded594ab4782214e to your computer and use it in GitHub Desktop.
import Foundation
// MARK: - Protocols
@objc protocol NSMethodSignaturePrivate {
static func signature(objCTypes: UnsafePointer<CChar>) -> NSMethodSignaturePrivate?
var numberOfArguments: Int { get }
@objc(getArgumentTypeAtIndex:) func getArgumentType(at index: Int) -> UnsafePointer<CChar>
var frameLength: Int { get }
var isOneway: ObjCBool { get }
var methodReturnType: UnsafePointer<CChar> { get }
var methodReturnLength: Int { get }
}
@objc protocol NSInvocationPrivate {
static func invocation(methodSignature: NSMethodSignaturePrivate) -> NSInvocationPrivate
var methodSignature: NSMethodSignaturePrivate { get }
func retainArguments()
var argumentsRetained: ObjCBool { get }
var target: AnyObject { get set }
var selector: Selector { get set }
func getReturnValue(_ location: UnsafeMutableRawPointer)
func setReturnValue(_ location: UnsafeRawPointer)
@objc(getArgument:atIndex:) func getArgument(_ location: UnsafeMutableRawPointer, at index: Int)
@objc(setArgument:atIndex:) func setArgument(_ location: UnsafeRawPointer, at index: Int)
func invoke()
func invoke(target: AnyObject)
}
@objc protocol NSObjectPrivate {
@objc(methodSignatureForSelector:) func methodSignature(for selector: Selector) -> NSMethodSignaturePrivate?
func forwardInvocation(_ invocation: NSInvocationPrivate)
}
// MARK: - Import class helper
func importClass<ProtocolType>(_ className: String) -> ProtocolType {
let typeNameSuffix = ".Type"
let protocolTypeName = String(reflecting: ProtocolType.self)
guard protocolTypeName.hasSuffix(typeNameSuffix) else {
preconditionFailure("Type `\(protocolTypeName)` is not a protocol type.")
}
let protocolName = protocolTypeName.dropLast(typeNameSuffix.count)
guard let `protocol` = protocolName.withCString(objc_getProtocol) else {
preconditionFailure("Type `\(protocolName)` is not an objc protocol.")
}
guard let `class` = className.withCString(objc_getClass) as? AnyClass else {
preconditionFailure("Class `\(className)` not found.")
}
if !class_addProtocol(`class`, `protocol`) {
assertionFailure("Failed to attach protocol `\(protocolName)` to class `\(className)`.")
}
guard let result = `class` as? ProtocolType else {
fatalError("Failed to cast class `\(className)` to protocol `\(protocolName)`.")
}
return result
}
// MARK: - Import classes
let NSMethodSignatureClass = importClass("NSMethodSignature") as NSMethodSignaturePrivate.Type
let NSInvocationClass = importClass("NSInvocation") as NSInvocationPrivate.Type
let NSObjectClass = importClass("NSObject") as NSObjectPrivate.Type
// MARK: - Try out the NSInvocation
let object = NSDate()
let objectPrivate = object as! NSObjectPrivate
let selector = Selector("description")
let signature = objectPrivate.methodSignature(for: selector)!
let invocation = NSInvocationClass.invocation(methodSignature: signature)
invocation.selector = selector
invocation.invoke(target: object)
var unmanagedResult: Unmanaged<NSString>? = nil
invocation.getReturnValue(&unmanagedResult)
let result = unmanagedResult?.takeRetainedValue()
print(result ?? "<nil>")
@tonyarnold
Copy link

Does this approach still work under recent versions of Swift? It seems to be fine until I need to cast an existing object to the private protocol, and then that fails.

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