Swift Method Swizzling (initializer, class method, instance method, getter, setter)


Swift 5のSwizzleについて1つのサンプルにまとめました。

自分もデバッグや一時的な動作変更に使う程度なので、間違いや補足などありましたらご指摘願います。


## UIView+Swizzle.swift

```swift
import UIKit

extension UIView {

    private struct SwizzleStatic {
        static var once = true
    }

    class func swizzle() {
        guard SwizzleStatic.once else {
            return
        }
        SwizzleStatic.once = false
        let swizzleMethod = { (original: Selector, swizzled: Selector) in
            guard let originalMethod = class_getInstanceMethod(self, original),
                let swizzledMethod = class_getInstanceMethod(self, swizzled) else {
                    return
            }
            method_exchangeImplementations(originalMethod, swizzledMethod)
        }
        let swizzleClassMethod = { (original: Selector, swizzled: Selector) in
            guard let originalMethod = class_getClassMethod(self, original),
                let swizzledMethod = class_getClassMethod(self, swizzled) else {
                    assertionFailure("The methods are not found!")
                    return
            }
            method_exchangeImplementations(originalMethod, swizzledMethod)
        }
        // イニシャライザ
        swizzleMethod(#selector(UIView.init(frame:)),
                      #selector(UIView.swizzled_init(frame:)))
        // クラスメソッド
        swizzleClassMethod(#selector(UIView.animate(withDuration:delay:options:animations:completion:)),
                           #selector(UIView.swizzled_animate(withDuration:delay:options:animations:completion:)))
        // インスタンスメソッド
        swizzleMethod(#selector(UIView.addSubview(_:)),
                      #selector(UIView.swizzled_addSubview(_:)))
        // プロパティ getter
        swizzleMethod(#selector(getter: UIView.frame),
                      #selector(UIView.swizzled_get_frame))
        // プロパティ setter
        swizzleMethod(#selector(setter: UIView.frame),
                      #selector(UIView.swizzled_set_frame(_:)))
    }

    // イニシャライザを上書き
    @objc func swizzled_init(frame: CGRect) -> UIView {
        let instance = swizzled_init(frame: frame)
        print("\(type(of: self)):\(#function):\(#line), \( instance )")
        return instance
    }

    // クラスメソッドを上書き
    @objc class func swizzled_animate(withDuration duration: TimeInterval, delay: TimeInterval, options: UIView.AnimationOptions = [], animations: @escaping () -> Void, completion: ((Bool) -> Void)? = nil) {
        print("\(type(of: self)):\(#function):\(#line), \( duration )")
        swizzled_animate(withDuration: duration, delay: delay, options: options, animations: animations, completion: completion)
    }

    // インスタンスメソッドを上書き
    @objc func swizzled_addSubview(_ view: UIView) {
        print("\(type(of: self)):\(#function):\(#line), \( view )")
        swizzled_addSubview(view)
    }

    // プロパティ getter を上書き
    @objc func swizzled_get_frame() -> CGRect {
        let frame = swizzled_get_frame()
        print("\(type(of: self)):\(#function):\(#line), \( frame )")
        return frame
    }

    // プロパティ setter を上書き
    @objc func swizzled_set_frame(_ frame: CGRect) {
        print("\(type(of: self)):\(#function):\(#line), \( frame )")
        swizzled_set_frame(frame)
    }

    // 新しいプロパティを追加
    private static var swizzleKeyName = "name"
    var swizzledName: String? {
        get {
            objc_getAssociatedObject(self, &UIView.swizzleKeyName) as? String
        }
        set {
            objc_setAssociatedObject(self, &UIView.swizzleKeyName, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }

}
```

イニシャライザとクラスメソッドは一部のクラスで指定方法に違いがあるようです。

## 有効化
上記`UIView+Swizzle.swift`をプロジェクト内に置き、AppDelegateの`application(_:didFinishLaunchingWithOptions:)`で呼び出すだけです。

```swift
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
        UIView.swizzle()
        ...
        return true
    }
```