@dynamicReplacement causes infinite recursion?

I did a quick experiment using two UICollectionView methods as a guinea pig.

The first one:

    // UICollectionViewCell's
    @objc(_bridgedUpdateConfigurationUsingState:)
    dynamic open func updateConfiguration(using state: UICellConfigurationState)

Note "dynamic" keyword, it is important and allows me to write:

extension UICollectionViewCell {
    @_dynamicReplacement(for: updateConfiguration(using:))
    func myUpdateConfiguration(using state: UICellConfigurationState) {
        print("enter myUpdateConfiguration")
        updateConfiguration(using: state)
        print("exit myUpdateConfiguration")
    }
}

and without doing anything else myUpdateConfiguration will be called instead of the original method. Note that within it I call the original method via the original name. This is new in Swift 5.something "native swift swizzling".


Now to another dude:

    // UICollectionViewCell's
    open func dragStateDidChange(_ dragState: UICollectionViewCell.DragState)

If you try the same approach as above:

extension UICollectionViewCell {
    // MARK: wrong. don't do that ❌
    @_dynamicReplacement(for: dragStateDidChange(_:))
    func wrong_dragStateDidChange(_ dragState: UICollectionViewCell.DragState) {
        print("enter myDragStateDidChange")
        dragStateDidChange(dragState)
        print("exit myDragStateDidChange")
    }
}

This won't work (won't be called). The reason is – the method is not mark as dynamic. Old Obj-C swizzling to the rescue:

extension UICollectionViewCell {
    // MARK: ✅
    @objc func correct_dragStateDidChange(_ dragState: UICollectionViewCell.DragState) {
        print("enter myDragStateDidChange")
        correct_dragStateDidChange(dragState)
        print("exit myDragStateDidChange")
    }
}
...
// call this somewhere once, early in the app lifecycle:
    let original = class_getInstanceMethod(UICollectionViewCell.self, #selector(UICollectionViewCell.dragStateDidChange(_:)))!
    let swizzled = class_getInstanceMethod(UICollectionViewCell.self, #selector(UICollectionViewCell.correct_dragStateDidChange(_:)))!
    method_exchangeImplementations(original, swizzled)

Note that within the implementation I'm calling the original method via the new name: which looks like recursion but it is not. If you were to call the original method via the original name – you'll get the infinite recursion you are talking about.

Hope this helps.

1 Like