Skip to content

Instantly share code, notes, and snippets.

@hpinhal
Created July 23, 2020 10:10
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save hpinhal/b58d019806d588dbb5330f0950ab8275 to your computer and use it in GitHub Desktop.
Save hpinhal/b58d019806d588dbb5330f0950ab8275 to your computer and use it in GitHub Desktop.
Performing some swizzling magic on your AppDelegate
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