Example implementation using runtime exchange method in swift
- 2020-06-19 11:48:43
- OfStack
preface
Runtime introduction
Learning something requires at least knowing what it is. You've probably heard that "runtime is a feature of ES6en-ES7en", and by "runtime" you mean runtime.
The old way, initialize, is no longer applicable and needs to be replaced by a new way.
Idea: Define a startup protocol, in app to complete the startup method to do method swizzle class to run the 1 side protocol method
1 kind
1, Step One
protocol SelfAware: class {
static func awake()
}
class NothingToSeeHere {
static func harmlessFunction() {
let typeCount = Int(objc_getClassList(nil, 0))
let types = UnsafeMutablePointer<AnyClass?>.allocate(capacity: typeCount)
let autoreleasingTypes = AutoreleasingUnsafeMutablePointer<AnyClass?>(types)
objc_getClassList(autoreleasingTypes, Int32(typeCount))
for index in 0 ..< typeCount { (types[index] as? SelfAware.Type)?.awake() }
types.deallocate(capacity: typeCount)
}
}
2, step two
extension UIApplication {
private static let runOnce: Void = {
NothingToSeeHere.harmlessFunction()
}()
override open var next: UIResponder? {
// Called before applicationDidFinishLaunching
UIApplication.runOnce
return super.next
}
}
3, step three
Follow protocol SelfAware and implement awake()
Type 2 (similar to type 1)
1. Create a protocol for swizzle injection
public protocol SwizzlingInjection: class {
static func inject()
}
2. Create swizzle helper
open class SwizzlingManager {
// Can only call 1 Method of time
private static let doOnce: Any? = {
UIViewController.inject()
return nil
}()
open static func enableInjection() {
_ = SwizzlingManager.doOnce
}
}
3. Create a class for UIApplication and call that method once
extension UIApplication{
open override var next: UIResponder?{
SwizzlingManager.enableInjection()
return super.next
}
}
4. Follow the injection protocol in the classes you need
extension UIViewController: SwizzlingInjection{
public static func inject() {
// Make sure it's not a subclass
guard self === UIViewController.self else { return }
DispatchQueue.once(token: "com.moglo.urmoji.UIViewController") {
//do swizzle method
}
}
}
once executes the method only once
public extension DispatchQueue {
private static var _onceTracker = [String]()
public class func once(file: String = #file, function: String = #function, line: Int = #line, block:()->Void) {
let token = file + ":" + function + ":" + String(line)
once(token: token, block: block)
}
/**
Executes a block of code, associated with a unique token, only once. The code is thread safe and will
only execute the code once even in the presence of multithreaded calls.
- parameter token: A unique reverse DNS style name such as com.vectorform.<name> or a GUID
- parameter block: Block to execute once
*/
public class func once(token: String, block:()->Void) {
objc_sync_enter(self)
defer { objc_sync_exit(self) }
if _onceTracker.contains(token) {
return
}
_onceTracker.append(token)
block()
}
//delay
typealias Task = (_ cancel : Bool) -> Void
@discardableResult
static func delay(time : TimeInterval, task: @escaping () -> ()) -> Task? {
func dispatch_later(block : @escaping () -> ()) {
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + time , execute: block)
}
var closure : (() -> ())? = task
var result : Task?
let delayedClosure : Task = {
cancel in
if let internalClosure = closure {
if cancel == false {
DispatchQueue.main.async(execute: internalClosure)
}
}
closure = nil
result = nil
}
result = delayedClosure
dispatch_later { () -> () in
if let delayedClosure = result {
delayedClosure(false)
}
}
return result
}
static func cancel(task : Task?) {
task?(true)
}
}
conclusion