[ad_1]
How do modules (plugins) work?
Would not be cool in case you might create objects that would work collectively with out realizing about one another? Think about that you’re constructing a dynamic kind. Based mostly on some inside situations, the fields are going to be composed utilizing the information coming from the enabled modules.
For instance you’ve module A, B, C, the place A is offering you Area 1, 2, 3, the B module is taking good care of Area 4, 5 and C is the supplier of Area 6. Now in case you flip off B, you need to solely be capable to see area 1, 2, 3 and 6. If every part is turned on you need 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 by means of 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 comparable utilizing Swift? 🤔
A hook system implementation
First we begin with a protocol with some extent of invocation. This methodology can be referred to as by the module supervisor to invoke the correct hook perform by title. We will go round a dictionary of parameters, so our hooks can have arguments. We’re utilizing the Any sort right here as a worth, so you may ship something as a parameter underneath a given key.
protocol Module {
func invoke(title: String, params: [String: Any]) -> Any?
}
extension Module {
func invoke(title: 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(title: String, params: [String: Any]) -> Any? {
swap title {
case "example_form":
return self.exampleFormHook()
default:
return nil
}
}
non-public func exampleFormHook() -> [String] {
["Field 1", "Field 2", "Field 3"]
}
}
class B: Module {
func invoke(title: String, params: [String: Any]) -> Any? {
swap title {
case "example_form":
return self.exampleFormHook()
default:
return nil
}
}
non-public func exampleFormHook() -> [String] {
["Field 4", "Field 5"]
}
}
class C: Module {
func invoke(title: String, params: [String: Any]) -> Any? {
swap title {
case "example_form":
return self.exampleFormHook()
default:
return nil
}
}
non-public func exampleFormHook() -> [String] {
["Field 6"]
}
}
Subsequent we want a module supervisor that may be initialized with an array of modules. This supervisor can be answerable for calling the appropriate invocation methodology on each single module and it will deal with the returned response in a type-safe method. We will implement two invoke methodology variations straight away. One for merging the consequence 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>(_ title: String, sort: T.Sort, params: [String: Any] = [:]) -> [T] {
let consequence = self.modules.map { module in
module.invoke(title: title, params: params)
}
return consequence.compactMap { $0 as? [T] }.flatMap { $0 }
}
func invokeHook<T>(_ title: String, sort: T.Sort, params: [String: Any] = [:]) -> T? {
for module in self.modules {
let consequence = module.invoke(title: title, params: params)
if consequence != nil {
return consequence as? T
}
}
return nil
}
}
You should utilize the the invokeAllHooks methodology to merge collectively an array of a generic sort. That is the one which we are able to use to assemble 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 methodology you may obtain an identical habits just like the chain of duty design sample. The responder chain works very comparable 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(title: String, params: [String: Any]) -> Any? {
swap title {
case "example_form":
return self.exampleFormHook()
case "example_responder":
return self.exampleResponderHook()
default:
return nil
}
}
non-public func exampleFormHook() -> [String] {
["Field 4", "Field 5"]
}
non-public func exampleResponderHook() -> String {
"Hiya, that is module B."
}
}
If we set off the brand new example_responder hook with the invokeHook methodology on each managers we’ll see that the 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’ve got an implementation in one in all our modules for this hook, the return worth can be current, so we are able to print it. Within the second case there isn’t any 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. 😜
[ad_2]
