Modules and hooks in Swift

0
8


How do modules (plugins) work?

Would not be cool if you happen to might create objects that might work collectively with out figuring out about one another? Think about that you’re constructing a dynamic kind. Primarily based on some inside situations, the fields are going to be composed utilizing the information coming from the enabled modules.

For instance you will have module A, B, C, the place A is offering you Discipline 1, 2, 3, the B module is taking good care of Discipline 4, 5 and C is the supplier of Discipline 6. Now if you happen to flip off B, it’s best to solely have the ability to see area 1, 2, 3 and 6. If every part is turned on it’s best to see all of the fields from 1 to six.

We will apply this very same sample to many issues. Simply take into consideration one of many greatest plugin ecosystem. WordPress is utilizing hooks to increase the core functinalities via them. It is all primarily based on the idea I simply talked about above. That is a part of the event-driven structure design sample. Now the query is how will we implement one thing related utilizing Swift? ?

A hook system implementation

First we begin with a protocol with a degree of invocation. This technique might be known as by the module supervisor to invoke the correct hook operate by identify. We’ll go round a dictionary of parameters, so our hooks can have arguments. We’re utilizing the Any sort right here as a price, so you may ship something as a parameter beneath a given key.

protocol Module {
    func invoke(identify: String, params: [String: Any]) -> Any?
}

extension Module {
    func invoke(identify: String, params: [String: Any]) -> Any? { nil }
}

Now let’s implement our modules utilizing a simplified model primarily based on the shape instance. ?

class A: Module {

    func invoke(identify: String, params: [String: Any]) -> Any? {
        swap identify {
        case "example_form":
            return self.exampleFormHook()
        default:
            return nil
        }
    }

    personal func exampleFormHook() -> [String] {
        ["Field 1", "Field 2", "Field 3"]
    }
}

class B: Module {
    func invoke(identify: String, params: [String: Any]) -> Any? {
        swap identify {
        case "example_form":
            return self.exampleFormHook()
        default:
            return nil
        }
    }

    personal func exampleFormHook() -> [String] {
        ["Field 4", "Field 5"]
    }
}

class C: Module {
    func invoke(identify: String, params: [String: Any]) -> Any? {
        swap identify {
        case "example_form":
            return self.exampleFormHook()
        default:
            return nil
        }
    }

    personal func exampleFormHook() -> [String] {
        ["Field 6"]
    }
}

Subsequent we want a module supervisor that may be initialized with an array of modules. This supervisor might be chargeable for calling the appropriate invocation technique on each single module and it will deal with the returned response in a type-safe method. We’ll implement two invoke technique variations instantly. One for merging the outcome and the opposite to return the primary results of a hook.

You may attempt to implement a model that may merge Bool values utilizing the && operator

Right here is our module supervisor implementation with the 2 generic strategies:

struct ModuleManager {

    let  modules: [Module]
    
    func invokeAllHooks<T>(_ identify: String, sort: T.Kind, params: [String: Any] = [:]) -> [T] {
        let outcome = self.modules.map { module in
            module.invoke(identify: identify, params: params)
        }
        return outcome.compactMap { $0 as? [T] }.flatMap { $0 }
    }

    func invokeHook<T>(_ identify: String, sort: T.Kind, params: [String: Any] = [:]) -> T? {
        for module in self.modules {
            let outcome = module.invoke(identify: identify, params: params)
            if outcome != nil {
                return outcome as? T
            }
        }
        return nil
    }
}

You need to use the the invokeAllHooks technique to merge collectively an array of a generic sort. That is the one which we are able to use to collect all he kind fields utilizing the underlying hook strategies.

let manager1 = ModuleManager(modules: [A(), B(), C()])
let form1 = manager1.invokeAllHooks("example_form", sort: String.self)
print(form1) 

let manager2 = ModuleManager(modules: [A(), C()])
let form2 = manager2.invokeAllHooks("example_form", sort: String.self)
print(form2) 

Utilizing the invokeHook technique you may obtain an identical habits just like the chain of accountability design sample. The responder chain works very related similiar, Apple makes use of responders on nearly each platform to deal with UI occasions. Let me present you the way it works by updating module B. ?

class B: Module {
    func invoke(identify: String, params: [String: Any]) -> Any? {
        swap identify {
        case "example_form":
            return self.exampleFormHook()
        case "example_responder":
            return self.exampleResponderHook()
        default:
            return nil
        }
    }

    personal func exampleFormHook() -> [String] {
        ["Field 4", "Field 5"]
    }
    
    personal func exampleResponderHook() -> String {
        "Hiya, that is module B."
    }
}

If we set off the brand new example_responder hook with the invokeHook technique on each managers we’ll see that the end result is sort of completely different.

if let worth = manager1.invokeHook("example_responder", sort: String.self) {
    print(worth) 
}

if let worth = manager2.invokeHook("example_responder", sort: String.self) {
    print(worth) 
}

Within the first case, since we have now an implementation in certainly one of our modules for this hook, the return worth might be current, so we are able to print it. Within the second case there isn’t a module to deal with the occasion, so the block contained in the situation will not be executed. Instructed ya’, it is like a responder chain. ?

LEAVE A REPLY

Please enter your comment!
Please enter your name here