-
-
Save victor-pavlychko/8a896917d8c73f4dded594ab4782214e to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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>") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.