Skip to content

Instantly share code, notes, and snippets.

@JunyuKuang
Last active October 14, 2022 03:43
Show Gist options
  • Save JunyuKuang/f26d91f588d3c6755ea03ea0eb5469a1 to your computer and use it in GitHub Desktop.
Save JunyuKuang/f26d91f588d3c6755ea03ea0eb5469a1 to your computer and use it in GitHub Desktop.
Prevent Mac Catalyst apps from running in the background for a long time, after user explicitly quit it.
//
// MacCatalystFastQuitHelper
//
// Copyright © 2022 Jonny Kuang. MIT
//
import UIKit
enum MacCatalystFastQuitHelper {
/// Enforces a maximum 3 seconds of execution time on all background tasks.
/// The app will be terminated within 3 seconds once user clicks the Quit App button.
static let setup: Void = {
#if targetEnvironment(macCatalyst)
swizzleInstanceMethod(
class: UIApplication.self,
originalSelector: #selector(UIApplication.beginBackgroundTask(expirationHandler:)),
swizzledSelector: #selector(UIApplication.kjy_swizzle_disable_beginBackgroundTask(expirationHandler:))
)
swizzleInstanceMethod(
class: UIApplication.self,
originalSelector: #selector(UIApplication.beginBackgroundTask(withName:expirationHandler:)),
swizzledSelector: #selector(UIApplication.kjy_swizzle_disable_beginBackgroundTask(withName:expirationHandler:))
)
#endif
}()
}
#if targetEnvironment(macCatalyst)
private extension UIApplication {
typealias Closure = () -> Void
@objc func kjy_swizzle_disable_beginBackgroundTask(expirationHandler: Closure?) -> UIBackgroundTaskIdentifier {
kjy_swizzle_disable_beginBackgroundTask(
expirationHandler: _resolved(expirationHandler: expirationHandler, taskName: nil)
)
}
@objc func kjy_swizzle_disable_beginBackgroundTask(withName taskName: String?, expirationHandler: Closure?) -> UIBackgroundTaskIdentifier {
kjy_swizzle_disable_beginBackgroundTask(
withName: taskName,
expirationHandler: _resolved(expirationHandler: expirationHandler, taskName: taskName)
)
}
func _resolved(expirationHandler: Closure?, taskName: String?) -> Closure {
var expirationHandler = expirationHandler
let endTask = {
let handler = expirationHandler
expirationHandler = nil
handler?()
}
if let taskName = taskName, taskName.contains("CoreData") {
// don't mess with CoreData
} else {
DispatchQueue.main.asyncAfter(deadline: .now() + 3, execute: endTask)
}
return endTask
}
}
#endif
//
// Swizzle.swift
//
// Copyright © 2022 Jonny Kuang. MIT
//
import Foundation
@discardableResult
public func swizzleInstanceMethod(class aClass: AnyClass?, originalSelector: Selector, swizzledSelector: Selector) -> Bool {
swizzleMethod(class: aClass, originalSelector: originalSelector, swizzledSelector: swizzledSelector, isClassMethod: false)
}
@discardableResult
public func swizzleClassMethod(class aClass: AnyClass?, originalSelector: Selector, swizzledSelector: Selector) -> Bool {
swizzleMethod(class: aClass, originalSelector: originalSelector, swizzledSelector: swizzledSelector, isClassMethod: true)
}
private func swizzleMethod(class aClass: AnyClass?, originalSelector: Selector, swizzledSelector: Selector, isClassMethod: Bool) -> Bool {
guard var aClass = aClass else {
assertionFailure("class not exist.")
return false
}
if isClassMethod {
if let _class = NSStringFromClass(aClass).withCString(objc_getMetaClass) as? AnyClass {
aClass = _class
} else {
assertionFailure("meta class not found.")
}
}
guard let originalMethod = isClassMethod ? class_getClassMethod(aClass, originalSelector) : class_getInstanceMethod(aClass, originalSelector),
let swizzledMethod = isClassMethod ? class_getClassMethod(aClass, swizzledSelector) : class_getInstanceMethod(aClass, swizzledSelector)
else {
assertionFailure("\(isClassMethod ? "class" : "instance") method unavailable. class: \(aClass) originalSelector: \(originalSelector) swizzledSelector: \(swizzledSelector)")
return false
}
if class_addMethod(aClass, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod)) {
class_replaceMethod(aClass, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))
} else {
method_exchangeImplementations(originalMethod, swizzledMethod)
}
return true
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment