Last active
October 14, 2022 03:43
-
-
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.
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
// | |
// 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 |
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
// | |
// 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