Search code examples
iosswiftprotocols

Swift closure in protocol extension


I want to Decorate UIViewController with the ability to adjust it's interface when setInteractionEnabled method is called from another class (ex. Network State Manager). All changes (if any) should be provided in the concrete controller by overriding onInteractionChanged. Here is my code:

import Foundation

typealias InteractionClosure = ((enabled: Bool) -> Void)

protocol Interaction: class {

    var onInteractionChanged: InteractionClosure? { get set }

    func setInteractionEnabled(enabled: Bool)

}

extension Interaction where Self: UIViewController {

    // Default: Do nothing
    // Throws: - Extensions may not contain stored properties
    var onInteractionChanged: InteractionClosure? = nil

    func setInteractionEnabled(enabled: Bool) {
        onInteractionChanged?(enabled: enabled)
    }

}

extension UIViewController : Interaction {}

How to add default implementation for onInteractionChanged?


Solution

  • The modern-swift compact way to use a closure in the protocol is as follows

    typealias InteractionClosure = (Bool) -> Void
    
    protocol Interaction {
        var interactionClosure: InteractionClosure? { get set }
    }
    
    
    class SomeViewController: UIViewController { }
    
    extension SomeViewController: Interaction {
        
        private struct AssociatedKeys {
            static var interactionClosure: UInt8 = 0
        }
    
        var interactionClosure: InteractionClosure? {
            get {
                let wrapper =
                objc_getAssociatedObject(self, &AssociatedKeys.interactionClosure) as? InteractionClosure
                return wrapper
            }
            set(newValue) {
                objc_setAssociatedObject(self, &AssociatedKeys.interactionClosure, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
            }
        }
    }
    

    Here we have no any global vars and supplementary functions.

    And we don't use protocol extension with default implementation because we need to set this closure to different UIViewController instances.

    If we used global var as key for associated value then we'd override closure setter for the first instance by the second one.

    Also the default implementation is not such obvious and the most times has to be associated with the self context (only global abstract defaults are good).