Swizzling UIImage init 不工作 iOS Swift

Swizzling UIImage init not working iOS Swift

我正在尝试调整 UIImage.init(named:) 但没有调用 init

extension UIImage {

    @objc public convenience init?(swizzledName: String) {
        self.init(named: swizzledName)

        /// Do something
        print("this is working")
    }

    static func swizzle() {
        guard let instance = class_getClassMethod(self, #selector(UIImage.init(named:))),
            let swizzledInstance = class_getClassMethod(self, #selector(UIImage.init(swizzledName:))) else { return }

        method_exchangeImplementations(instance, swizzledInstance)
    }
}

用法

UIImage.swizzle()
let image = UIImage(named: "avatar")

不工作

我在为 UIImage 调配这些 init 方法时也遇到了麻烦。我发现的唯一方法是改用 class 方法,这似乎工作正常(我首先尝试使用 static 方法,但没有用,所以我尝试了 class 方法,确实有效):

func swizzle(originalClass: AnyClass,
             originalSelector: Selector,
             isOriginalSelectorClassMethod: Bool,
             swizzledClass: AnyClass,
             swizzledSelector: Selector,
             isSwizzledSelectorClassMethod: Bool) {
    guard let originalMethod = isOriginalSelectorClassMethod ?
        class_getClassMethod(originalClass, originalSelector) :
        class_getInstanceMethod(originalClass, originalSelector) else {
            return
    }

    guard let swizzledMethod = isSwizzledSelectorClassMethod ?
        class_getClassMethod(swizzledClass, swizzledSelector) :
        class_getInstanceMethod(swizzledClass, swizzledSelector) else {
            return
    }

    let didAddMethod = class_addMethod(isOriginalSelectorClassMethod ? object_getClass(originalClass)! : originalClass,
                                       originalSelector,
                                       method_getImplementation(swizzledMethod),
                                       method_getTypeEncoding(swizzledMethod))

    if didAddMethod {
        class_replaceMethod(isSwizzledSelectorClassMethod ? object_getClass(swizzledClass)! : swizzledClass,
                            swizzledSelector,
                            method_getImplementation(originalMethod),
                            method_getTypeEncoding(originalMethod))
    } else {
        method_exchangeImplementations(originalMethod, swizzledMethod)
    }
}

extension UIImage {
    static func swizzleInitializersIfNeeded() {
        guard !areInitializersSwizzled else {
            return
        }

        areInitializersSwizzled = true

        swizzle(originalClass: self,
                originalSelector: #selector(UIImage.init(named:)),
                isOriginalSelectorClassMethod: true,
                swizzledClass: self,
                swizzledSelector: #selector(UIImage.image(named:)),
                isSwizzledSelectorClassMethod: true)

        swizzle(originalClass: self,
                originalSelector: #selector(UIImage.init(named:in:with:)),
                isOriginalSelectorClassMethod: true,
                swizzledClass: self,
                swizzledSelector: #selector(UIImage.image(named:in:with:)),
                isSwizzledSelectorClassMethod: true)
    }

    private static var areInitializersSwizzled = false

    @objc fileprivate class func image(named name: String) -> UIImage? {
        let image = self.image(named: name)
        image?.name = name

        return image
    }

    @objc fileprivate class func image(named name: String,
                                       in bundle: Bundle,
                                       with config: UIImage.Configuration) -> UIImage? {
        let image = self.image(named: name, in: bundle, with: config)
        image?.name = name
        image?.bundle = bundle

        return image
    }

    private static var nameKey = 0
    private static var bundleKey = 0

    private(set) var name: String? {
        get { objc_getAssociatedObject(self, &UIImage.nameKey) as? String }
        set { objc_setAssociatedObject(self, &UIImage.nameKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
    }

    private(set) var bundle: Bundle? {
        get { objc_getAssociatedObject(self, &UIImage.bundleKey) as? Bundle }
        set { objc_setAssociatedObject(self, &UIImage.bundleKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
    }
}

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        UIImage.swizzleInitializersIfNeeded()

        let image = UIImage(named: "test_image")

        print(image?.name as Any) // prints Optional("test_image")
    }
}