Created
July 23, 2020 10:10
-
-
Save hpinhal/b58d019806d588dbb5330f0950ab8275 to your computer and use it in GitHub Desktop.
Performing some swizzling magic on your AppDelegate
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 UIKit | |
private typealias ApplicationDidBecomeActive = @convention(c) (Any, Selector, UIApplication) -> Void | |
private typealias ApplicationWillResignActive = @convention(c) (Any, Selector, UIApplication) -> Void | |
private struct AssociatedObjectKeys { | |
static var originalClass = "MyFramework_OriginalClass" | |
static var originalImplementations = "MyFramework_OriginalImplementations" | |
} | |
private var gOriginalAppDelegate: UIApplicationDelegate? | |
private var gAppDelegateSubClass: AnyClass? | |
public class AppDelegateSwizzler: NSProxy { | |
public static func setup() { | |
// Let the property be initialized and run its block. | |
_ = runOnce | |
} | |
/// Using Swift's lazy evaluation of a static property we get the same | |
/// thread-safety and called-once guarantees as dispatch_once provided. | |
private static let runOnce: () = { | |
weak var appDelegate = UIApplication.shared.delegate | |
proxyAppDelegate(appDelegate) | |
}() | |
private static func proxyAppDelegate(_ appDelegate: UIApplicationDelegate?) { | |
guard let appDelegate = appDelegate else { | |
NSLog("Cannot proxy AppDelegate. Instance is nil.") | |
return | |
} | |
gAppDelegateSubClass = createSubClass(from: appDelegate) | |
self.reassignAppDelegate() | |
} | |
private static func reassignAppDelegate() { | |
weak var delegate = UIApplication.shared.delegate | |
UIApplication.shared.delegate = nil | |
UIApplication.shared.delegate = delegate | |
gOriginalAppDelegate = delegate | |
// TODO observe UIApplication | |
} | |
private static func createSubClass(from originalDelegate: UIApplicationDelegate) -> AnyClass? { | |
let originalClass = type(of: originalDelegate) | |
let newClassName = "\(originalClass)_\(UUID().uuidString)" | |
guard NSClassFromString(newClassName) == nil else { | |
NSLog("Cannot create subclass. Subclass already exists.") | |
return nil | |
} | |
guard let subClass = objc_allocateClassPair(originalClass, newClassName, 0) else { | |
NSLog("Cannot create subclass. Subclass already exists.") | |
return nil | |
} | |
self.createMethodImplementations(in: subClass, withOriginalDelegate: originalDelegate) | |
self.overrideDescription(in: subClass) | |
// Store the original class | |
objc_setAssociatedObject(originalDelegate, &AssociatedObjectKeys.originalClass, originalClass, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) | |
guard class_getInstanceSize(originalClass) == class_getInstanceSize(subClass) else { | |
NSLog("Cannot create subclass. Original class' and subclass' sizes do not match.") | |
return nil | |
} | |
objc_registerClassPair(subClass) | |
if object_setClass(originalDelegate, subClass) != nil { | |
NSLog("Successfully created proxy.") | |
} | |
return subClass | |
} | |
private static func createMethodImplementations( | |
in subClass: AnyClass, | |
withOriginalDelegate originalDelegate: UIApplicationDelegate | |
) { | |
let originalClass = type(of: originalDelegate) | |
var originalImplementationsStore: [String: NSValue] = [:] | |
// For applicationDidBecomeActive: | |
let applicationDidBecomeActiveSelector = #selector(applicationDidBecomeActive(_:)) | |
self.proxyInstanceMethod( | |
toClass: subClass, | |
withSelector: applicationDidBecomeActiveSelector, | |
fromClass: AppDelegateSwizzler.self, | |
fromSelector: applicationDidBecomeActiveSelector, | |
withOriginalClass: originalClass, | |
storeOriginalImplementationInto: &originalImplementationsStore) | |
// For applicationWillResignActive: | |
let applicationWillResignActiveSelector = #selector(applicationWillResignActive(_:)) | |
self.proxyInstanceMethod( | |
toClass: subClass, | |
withSelector: applicationWillResignActiveSelector, | |
fromClass: AppDelegateSwizzler.self, | |
fromSelector: applicationWillResignActiveSelector, | |
withOriginalClass: originalClass, | |
storeOriginalImplementationInto: &originalImplementationsStore) | |
// Store original implementations | |
objc_setAssociatedObject(originalDelegate, &AssociatedObjectKeys.originalImplementations, originalImplementationsStore, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) | |
} | |
private static func overrideDescription(in subClass: AnyClass) { | |
// Override the description so the custom class name will not show up. | |
self.addInstanceMethod( | |
toClass: subClass, | |
toSelector: #selector(description), | |
fromClass: AppDelegateSwizzler.self, | |
fromSelector: #selector(originalDescription)) | |
} | |
private static func proxyInstanceMethod( | |
toClass destinationClass: AnyClass, | |
withSelector destinationSelector: Selector, | |
fromClass sourceClass: AnyClass, | |
fromSelector sourceSelector: Selector, | |
withOriginalClass originalClass: AnyClass, | |
storeOriginalImplementationInto originalImplementationsStore: inout [String: NSValue] | |
) { | |
self.addInstanceMethod( | |
toClass: destinationClass, | |
toSelector: destinationSelector, | |
fromClass: sourceClass, | |
fromSelector: sourceSelector) | |
let sourceImplementation = methodImplementation(for: destinationSelector, from: originalClass) | |
let sourceImplementationPointer = NSValue(pointer: UnsafePointer(sourceImplementation)) | |
let destinationSelectorStr = NSStringFromSelector(destinationSelector) | |
originalImplementationsStore[destinationSelectorStr] = sourceImplementationPointer | |
} | |
private static func addInstanceMethod( | |
toClass destinationClass: AnyClass, | |
toSelector destinationSelector: Selector, | |
fromClass sourceClass: AnyClass, | |
fromSelector sourceSelector: Selector | |
) { | |
let method = class_getInstanceMethod(sourceClass, sourceSelector)! | |
let methodImplementation = method_getImplementation(method) | |
let methodTypeEncoding = method_getTypeEncoding(method) | |
if !class_addMethod(destinationClass, destinationSelector, methodImplementation, methodTypeEncoding) { | |
NSLog("Cannot copy method to destination selector '\(destinationSelector)' as it already exists.") | |
} | |
} | |
private static func methodImplementation(for selector: Selector, from fromClass: AnyClass) -> IMP? { | |
guard let method = class_getInstanceMethod(fromClass, selector) else { | |
return nil | |
} | |
return method_getImplementation(method) | |
} | |
private static func originalMethodImplementation(for selector: Selector, object: Any) -> NSValue? { | |
let originalImplementationsStore = objc_getAssociatedObject(object, &AssociatedObjectKeys.originalImplementations) as? [String: NSValue] | |
return originalImplementationsStore?[NSStringFromSelector(selector)] | |
} | |
@objc | |
private func originalDescription() -> String { | |
let originalClass: AnyClass = objc_getAssociatedObject(self, &AssociatedObjectKeys.originalClass) as! AnyClass | |
let originalClassName = NSStringFromClass(originalClass) | |
let pointerHex = String(format: "%p", unsafeBitCast(self, to: Int.self)) | |
return "<\(originalClassName): \(pointerHex)>" | |
} | |
@objc | |
private func applicationDidBecomeActive(_ application: UIApplication) { | |
NSLog("Framework: application did become active") | |
let methodSelector = #selector(applicationDidBecomeActive) | |
guard let pointer = AppDelegateSwizzler.originalMethodImplementation(for: methodSelector, object: self), | |
let pointerValue = pointer.pointerValue else { | |
NSLog("No original implementation for 'applicationDidBecomeActive'. Skipping...") | |
return | |
} | |
let originalImplementation = unsafeBitCast(pointerValue, to: ApplicationDidBecomeActive.self) | |
originalImplementation(self, methodSelector, application) | |
} | |
@objc | |
private func applicationWillResignActive(_ application: UIApplication) { | |
NSLog("Framework: application will resign active") | |
let methodSelector = #selector(applicationWillResignActive) | |
guard let pointer = AppDelegateSwizzler.originalMethodImplementation(for: methodSelector, object: self), | |
let pointerValue = pointer.pointerValue else { | |
NSLog("No original implementation for 'applicationWillResignActive'. Skipping...") | |
return | |
} | |
let originalImplementation = unsafeBitCast(pointerValue, to: ApplicationWillResignActive.self) | |
originalImplementation(self, methodSelector, application) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment