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


Related articles: