|
@@ -1,214 +0,0 @@
|
|
|
-//
|
|
|
-// Dynamic
|
|
|
-// Created by Mhd Hejazi on 4/15/20.
|
|
|
-// Copyright © 2020 Samabox. All rights reserved.
|
|
|
-//
|
|
|
-
|
|
|
-import Foundation
|
|
|
-
|
|
|
-class Invocation: NSObject {
|
|
|
-
|
|
|
- private let target: NSObject
|
|
|
- private let selector: Selector
|
|
|
-
|
|
|
- var invocation: NSObject?
|
|
|
-
|
|
|
- var numberOfArguments: Int = 0
|
|
|
- var returnLength: Int = 0
|
|
|
- var returnType: UnsafePointer<CChar>?
|
|
|
- var returnTypeString: String? {
|
|
|
- guard let returnType = returnType else { return nil }
|
|
|
- return String(cString: returnType)
|
|
|
- }
|
|
|
- var returnsObject: Bool {
|
|
|
- /// `@` is the type encoding for an object
|
|
|
- returnTypeString == "@"
|
|
|
- }
|
|
|
- var returnsAny: Bool {
|
|
|
- /// `v` is the type encoding for Void
|
|
|
- returnTypeString != "v"
|
|
|
- }
|
|
|
- lazy var returnedObject: AnyObject? = {
|
|
|
- returnedObjectValue()
|
|
|
- }()
|
|
|
- private(set) var isInvoked: Bool = false
|
|
|
-
|
|
|
- init(target: NSObject, selector: Selector) throws {
|
|
|
- self.target = target
|
|
|
- self.selector = selector
|
|
|
- super.init()
|
|
|
-
|
|
|
- try initialize()
|
|
|
- }
|
|
|
-
|
|
|
- private func initialize() throws {
|
|
|
- /// `NSMethodSignature *methodSignature = [target methodSignatureForSelector: selector]`
|
|
|
- let methodSignature: NSObject
|
|
|
- do {
|
|
|
- let selector = NSSelectorFromString("methodSignatureForSelector:")
|
|
|
- let signature = (@convention(c)(NSObject, Selector, Selector) -> Any).self
|
|
|
- let method = unsafeBitCast(target.method(for: selector), to: signature)
|
|
|
- guard let result = method(target, selector, self.selector) as? NSObject else {
|
|
|
- let error = InvocationError.unrecognizedSelector(type(of: target), self.selector)
|
|
|
- throw error
|
|
|
- }
|
|
|
- methodSignature = result
|
|
|
- }
|
|
|
-
|
|
|
- /// `numberOfArguments = methodSignature.numberOfArguments`
|
|
|
- self.numberOfArguments = methodSignature.value(forKeyPath: "numberOfArguments") as? Int ?? 0
|
|
|
-
|
|
|
- /// `methodReturnLength = methodSignature.methodReturnLength`
|
|
|
- self.returnLength = methodSignature.value(forKeyPath: "methodReturnLength") as? Int ?? 0
|
|
|
-
|
|
|
- /// `methodReturnType = methodSignature.methodReturnType`
|
|
|
- let methodReturnType: UnsafePointer<CChar>
|
|
|
- do {
|
|
|
- let selector = NSSelectorFromString("methodReturnType")
|
|
|
- let signature = (@convention(c)(NSObject, Selector) -> UnsafePointer<CChar>).self
|
|
|
- let method = unsafeBitCast(methodSignature.method(for: selector), to: signature)
|
|
|
- methodReturnType = method(methodSignature, selector)
|
|
|
- }
|
|
|
- self.returnType = methodReturnType
|
|
|
-
|
|
|
- /// `NSInvocation *invocation = [NSInvocation invocationWithMethodSignature: methodSignature]`
|
|
|
- let invocation: NSObject
|
|
|
- do {
|
|
|
- let NSInvocation = NSClassFromString("NSInvocation") as AnyObject
|
|
|
- let selector = NSSelectorFromString("invocationWithMethodSignature:")
|
|
|
- let signature = (@convention(c)(AnyObject, Selector, AnyObject) -> AnyObject).self
|
|
|
- let method = unsafeBitCast(NSInvocation.method(for: selector), to: signature)
|
|
|
- guard let result = method(NSInvocation, selector, methodSignature) as? NSObject else {
|
|
|
- let error = InvocationError.unrecognizedSelector(type(of: target), self.selector)
|
|
|
- throw error
|
|
|
- }
|
|
|
- invocation = result
|
|
|
- }
|
|
|
- self.invocation = invocation
|
|
|
-
|
|
|
- /// `invocation.selector = selector`
|
|
|
- do {
|
|
|
- let selector = NSSelectorFromString("setSelector:")
|
|
|
- let signature = (@convention(c)(NSObject, Selector, Selector) -> Void).self
|
|
|
- let method = unsafeBitCast(invocation.method(for: selector), to: signature)
|
|
|
- method(invocation, selector, self.selector)
|
|
|
- }
|
|
|
-
|
|
|
- /// `[invocation retainArguments]`
|
|
|
- do {
|
|
|
- let selector = NSSelectorFromString("retainArguments")
|
|
|
- let signature = (@convention(c)(NSObject, Selector) -> Void).self
|
|
|
- let method = unsafeBitCast(invocation.method(for: selector), to: signature)
|
|
|
- method(invocation, selector)
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- func setArgument(_ argument: Any?, at index: NSInteger) {
|
|
|
- guard let invocation = invocation else { return }
|
|
|
-
|
|
|
- /// `[invocation setArgument:&argument atIndex:i + 2]`
|
|
|
- let selector = NSSelectorFromString("setArgument:atIndex:")
|
|
|
- let signature = (@convention(c)(NSObject, Selector, UnsafeRawPointer, Int) -> Void).self
|
|
|
- let method = unsafeBitCast(invocation.method(for: selector), to: signature)
|
|
|
-
|
|
|
- if let valueArgument = argument as? NSValue {
|
|
|
- /// Get the type byte size
|
|
|
- let typeSize = UnsafeMutablePointer<Int>.allocate(capacity: 1)
|
|
|
- defer { typeSize.deallocate() }
|
|
|
- NSGetSizeAndAlignment(valueArgument.objCType, typeSize, nil)
|
|
|
-
|
|
|
- /// Get the actual value
|
|
|
- let buffer = UnsafeMutablePointer<Int8>.allocate(capacity: typeSize.pointee)
|
|
|
- defer { buffer.deallocate() }
|
|
|
- valueArgument.getValue(buffer)
|
|
|
-
|
|
|
- method(invocation, selector, buffer, index)
|
|
|
- } else {
|
|
|
- withUnsafePointer(to: argument) { pointer in
|
|
|
- method(invocation, selector, pointer, index)
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- func invoke() {
|
|
|
- guard let invocation = invocation, !isInvoked else { return }
|
|
|
-
|
|
|
- isInvoked = true
|
|
|
-
|
|
|
- /// `[invocation invokeWithTarget: target]`
|
|
|
- do {
|
|
|
- let selector = NSSelectorFromString("invokeWithTarget:")
|
|
|
- let signature = (@convention(c)(NSObject, Selector, AnyObject) -> Void).self
|
|
|
- let method = unsafeBitCast(invocation.method(for: selector), to: signature)
|
|
|
- method(invocation, selector, target)
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- func getReturnValue<T>(result: inout T) {
|
|
|
- guard let invocation = invocation else { return }
|
|
|
-
|
|
|
- /// `[invocation getReturnValue: returnValue]`
|
|
|
- do {
|
|
|
- let selector = NSSelectorFromString("getReturnValue:")
|
|
|
- let signature = (@convention(c)(NSObject, Selector, UnsafeMutableRawPointer) -> Void).self
|
|
|
- let method = unsafeBitCast(invocation.method(for: selector), to: signature)
|
|
|
- withUnsafeMutablePointer(to: &result) { pointer in
|
|
|
- method(invocation, selector, pointer)
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private func returnedObjectValue() -> AnyObject? {
|
|
|
- guard returnsObject, returnLength > 0 else {
|
|
|
- return nil
|
|
|
- }
|
|
|
-
|
|
|
- var result: AnyObject?
|
|
|
-
|
|
|
- getReturnValue(result: &result)
|
|
|
-
|
|
|
- guard let object = result else {
|
|
|
- return nil
|
|
|
- }
|
|
|
-
|
|
|
- /// Take the ownership of the initialized objects to ensure they're deallocated properly.
|
|
|
- if isRetainingMethod() {
|
|
|
- return Unmanaged.passRetained(object).takeRetainedValue()
|
|
|
- }
|
|
|
-
|
|
|
- /// `NSInvocation.getReturnValue()` doesn't give us the ownership of the returned object, but the compiler
|
|
|
- /// tries to release this object anyway. So, we are retaining it to balance with the compiler's release.
|
|
|
- return Unmanaged.passRetained(object).takeUnretainedValue()
|
|
|
- }
|
|
|
-
|
|
|
- private func isRetainingMethod() -> Bool {
|
|
|
- /// Refer to: https://bit.ly/308okXm
|
|
|
- let selector = NSStringFromSelector(self.selector)
|
|
|
- return selector == "alloc" ||
|
|
|
- selector.hasPrefix("new") ||
|
|
|
- selector.hasPrefix("copy") ||
|
|
|
- selector.hasPrefix("mutableCopy")
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-public enum InvocationError: CustomNSError {
|
|
|
- case unrecognizedSelector(_ classType: AnyClass, _ selector: Selector)
|
|
|
-
|
|
|
- public static var errorDomain: String { String(describing: Invocation.self) }
|
|
|
-
|
|
|
- public var errorCode: Int {
|
|
|
- switch self {
|
|
|
- case .unrecognizedSelector:
|
|
|
- return 404
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- public var errorUserInfo: [String: Any] {
|
|
|
- var message: String
|
|
|
- switch self {
|
|
|
- case .unrecognizedSelector(let classType, let selector):
|
|
|
- message = "'\(String(describing: classType))' doesn't recognize selector '\(selector)'"
|
|
|
- }
|
|
|
- return [NSLocalizedDescriptionKey: message]
|
|
|
- }
|
|
|
-}
|