Design Patterns in WKWebView: Builder, Strategy & Chain of Responsibility Explained
Explore how to apply Builder, Strategy, and Chain of Responsibility patterns to streamline WKWebView encapsulation in iOS, solving complex UI logic and improving code maintainability effectively.
点击这里查看本文章简体中文版本。
點擊這裡查看本文章正體中文版本。
This post was translated with AI assistance — let me know if anything sounds off!
Practical Application Record of Design Patterns—In WKWebView with Builder, Strategy & Chain of Responsibility Pattern
Design Patterns Used When Encapsulating iOS WKWebView (Strategy, Chain of Responsibility, Builder).
Photo by Dean Pugh
About Design Patterns
Before discussing Design Patterns, it’s important to mention that the classic GoF 23 design patterns were published over 30 years ago (1994). With changes in tools, languages, and software development approaches, the landscape has evolved significantly. Many new design patterns have since emerged in different fields. Design Patterns are neither a cure-all nor the only solution. They serve more as a “language shorthand” to apply the right pattern in the right context, reducing collaboration barriers during development. For example, applying the Strategy Pattern here allows future maintenance and extension to follow the Strategy Pattern structure directly. Most design patterns also provide good decoupling, which greatly benefits extensibility and testability.
Mindset for Using Design Patterns
Not the only solution
Not a cure-all
Do not apply rigidly; choose the appropriate design pattern based on the type of problem to solve (creation? behavior? structure?) and the purpose.
Do not drastically alter the code. Major changes can cause confusion for future maintainers. Just like language, everyone calls Apple “Apple.” If you define it as “Banana,” it becomes an additional development cost that requires special knowledge.
Avoid using keywords whenever possible. For example, the Factory Pattern is commonly named
XXXFactory
. If it is not a factory pattern, this naming keyword should not be used.Be cautious when creating your own patterns. Although there are only 23 classic ones, many new patterns have emerged through years of evolution across various fields. You can first refer to online resources to find suitable patterns (after all, three heads are better than one). If none fit, then propose new design patterns and try to publish them for review and adjustment by people from different fields and contexts.
Code is ultimately written for people to maintain. As long as it is easy to maintain and extend, using design patterns is not always necessary.
The team must have a consensus on Design Patterns before using them.
Design Patterns can be combined with other Design Patterns for advanced techniques.
Getting started with Design Patterns requires continuous practical experience to develop a sharper sense of which scenarios are suitable or unsuitable for their application.
Helpful Tool ChatGPT
Since the arrival of ChatGPT, learning practical applications of Design Patterns has become easier. Just describe your problem clearly and ask which design patterns suit the scenario. It can provide several possible patterns along with explanations. Although not every answer is perfect, it at least offers feasible directions. We only need to explore these patterns further in the context of our own practical issues, and we can eventually find a good solution!
Practical Use Cases of WKWebView Design Patterns
This practical application of Design Patterns focuses on consolidating the WKWebView object features within the current codebase and applying Design Patterns at several suitable logical abstraction points while developing a unified WKWebView component. This is a record of insights and experiences shared.
The complete demo project code will be attached at the end of the article.
Original Non-Abstract Writing Style
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
class WKWebViewController: UIViewController {
// MARK - Define some variables and switches for injecting features during external init...
// Simulate business logic: switch to match special paths to open native pages
let noNeedNativePresent: Bool
// Simulate business logic: switch for DeeplinkManager check
let deeplinkCheck: Bool
// Simulate business logic: is it the home page?
let isHomePage: Bool
// Simulate business logic: WKUserScript scripts to inject into WKWebView
let userScripts: [WKUserScript]
// Simulate business logic: WKScriptMessageHandler scripts to inject into WKWebView
let scriptMessageHandlers: [String: WKScriptMessageHandler]
// Allow overriding ViewController title from WebView title
let overrideTitleFromWebView: Bool
let url: URL
// ...
}
// ...
extension OldWKWebViewController: WKNavigationDelegate {
// MARK - iOS WKWebView navigationAction Delegate, used to decide how to handle the upcoming link
// Must call decisionHandler(.allow) or decisionHandler(.cancel) at the end
// decisionHandler(.cancel) will stop loading the upcoming page
// Here simulates different logic based on different variables and switches:
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
guard let url = navigationAction.request.url else {
decisionHandler(.allow)
return
}
// Simulate business logic: WebViewController deeplinkCheck == true (means need to check with DeepLinkManager and open page)
if deeplinkCheck {
print("DeepLinkManager.open(\(url.absoluteString)")
// Simulate DeepLinkManager logic, if URL can be opened successfully, open it and end the process.
// if DeepLinkManager.open(url) == true {
decisionHandler(.cancel)
return
// }
}
// Simulate business logic: WebViewController isHomePage == true (means it is home page) & WebView is browsing home page, then switch TabBar Index
if isHomePage {
if url.absoluteString == "https://zhgchg.li" {
print("Switch UITabBarController to Index 0")
decisionHandler(.cancel)
}
}
// Simulate business logic: WebViewController noNeedNativePresent == false (means need to match special paths to open native pages)
if !noNeedNativePresent {
if url.pathComponents.count >= 3 {
if url.pathComponents[1] == "product" {
// match http://zhgchg.li/product/1234
let id = url.pathComponents[2]
print("Present ProductViewController(\(id)")
decisionHandler(.cancel)
} else if url.pathComponents[1] == "shop" {
// match http://zhgchg.li/shop/1234
let id = url.pathComponents[2]
print("Present ShopViewController(\(id)")
decisionHandler(.cancel)
}
// more...
}
}
decisionHandler(.allow)
}
}
// ...
Question
Setting variables and toggles openly within a class makes it unclear which are meant for configuration.
Directly exposing the WKUserScript variable externally is not recommended; we want to control the injected JS and only allow specific behaviors to be injected.
Unable to control WKScriptMessageHandler registration rules.
If you need to initialize similar WebViews, you must repeatedly write the injection parameter rules, and the parameter rules cannot be reused.
Inside the
navigationAction Delegate
, the flow is controlled by variables. To delete or change the flow or order, you must modify the entire code, which may break the originally working flow.
Builder Pattern 建造者模式
Builder Pattern belongs to the creational design patterns. It separates the steps and logic of object creation, allowing the user to set parameters step by step and reuse configurations, ultimately creating the target object. Additionally, the same creation steps can produce different object implementations.
The above diagram uses making Pizza as an example. First, the steps to make Pizza are divided into several methods and declared in the PizzaBuilder
Protocol (Interface). The ConcretePizzaBuilder
is the object that actually makes the Pizza, which could be a Vegetarian PizzaBuilder
or a Meat PizzaBuilder
. Different Builders may use different ingredients, but they all ultimately build()
to produce a Pizza
object.
WKWebView Scenario
Back to the WKWebView scenario, our final output object is MyWKWebViewConfiguration
. We unify all the variables needed for WKWebView
settings into this object and use the Builder Pattern MyWKWebViewConfigurator
to gradually complete the construction of the Configuration.
1
2
3
4
5
6
7
8
public struct MyWKWebViewConfiguration {
let headNavigationHandler: NavigationActionHandler?
let scriptMessageStrategies: [ScriptMessageStrategy]
let userScripts: [WKUserScript]
let overrideTitleFromWebView: Bool
let url: URL
}
// All parameters are exposed only within the Module (Internal)
MyWKWebViewConfigurator (Builder Pattern)
Here, since I only need to build for MyWKWebView, I did not further split
MyWKWebViewConfigurator
into a Protocol (Interface).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public final class MyWKWebViewConfigurator {
private var headNavigationHandler: NavigationActionHandler? = nil
private var overrideTitleFromWebView: Bool = true
private var disableZoom: Bool = false
private var scriptMessageStrategies: [ScriptMessageStrategy] = []
public init() {
}
// Parameter encapsulation and internal control
public func set(disableZoom: Bool) -> Self {
self.disableZoom = disableZoom
return self
}
public func set(overrideTitleFromWebView: Bool) -> Self {
self.overrideTitleFromWebView = overrideTitleFromWebView
return self
}
public func set(headNavigationHandler: NavigationActionHandler) -> Self {
self.headNavigationHandler = headNavigationHandler
return self
}
// You can encapsulate additional logic rules here
public func add(scriptMessageStrategy: ScriptMessageStrategy) -> Self {
scriptMessageStrategies.removeAll(where: { type(of: $0).identifier == type(of: scriptMessageStrategy).identifier })
scriptMessageStrategies.append(scriptMessageStrategy)
return self
}
public func build(url: URL) -> MyWKWebViewConfiguration {
var userScripts:[WKUserScript] = []
// Attach only when generating
if disableZoom {
let script = "var meta = document.createElement('meta'); meta.name='viewport'; meta.content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no'; document.getElementsByTagName('head')[0].appendChild(meta);"
let disableZoomScript = WKUserScript(source: script, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
userScripts.append(disableZoomScript)
}
return MyWKWebViewConfiguration(headNavigationHandler: headNavigationHandler, scriptMessageStrategies: scriptMessageStrategies, userScripts: userScripts, overrideTitleFromWebView: overrideTitleFromWebView, url: url)
}
}
Adding an extra layer of separation can better utilize Access Control to restrict parameter usage. In this scenario, we want to allow direct injection of WKUserScript
into MyWKWebView
, but we don’t want to open it up so much that users can inject scripts arbitrarily. By combining the Builder Pattern with Swift Access Control, once MyWKWebView
is placed inside a module, MyWKWebViewConfigurator
exposes an operation method func set(disableZoom: Bool)
externally. Internally, when creating MyWKWebViewConfiguration
, it attaches the WKUserScript
. All parameters of MyWKWebViewConfiguration
are immutable from outside and can only be generated through MyWKWebViewConfigurator
.
MyWKWebViewConfigurator + Simple Factory
After having the MyWKWebViewConfigurator
Builder, we can create a simple factory to encapsulate and reuse the setup process.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct MyWKWebViewConfiguratorFactory {
enum ForType {
case `default`
case productPage
case payment
}
static func make(for type: ForType) -> MyWKWebViewConfigurator {
switch type {
case .default:
return MyWKWebViewConfigurator()
.add(scriptMessageStrategy: PageScriptMessageStrategy())
.set(overrideTitleFromWebView: false)
.set(disableZoom: false)
case .productPage:
return Self.make(for: .default).set(disableZoom: true).set(overrideTitleFromWebView: true)
case .payment:
return MyWKWebViewConfigurator().set(headNavigationHandler: paymentNavigationActionHandler)
}
}
}
Chain of Responsibility Pattern 責任鏈模式
The Chain of Responsibility Pattern belongs to behavioral design patterns. It encapsulates object handling operations and links them in a chain structure. Requests are passed along the chain until one handles them. The linked operation encapsulations can be freely combined and reordered.
The Chain of Responsibility focuses on whether you need to handle the incoming item; if not, just Skip , so it cannot process partially or modify the input object before passing it to the next. If that is the requirement, then it is another Interceptor Pattern.
The above diagram uses Tech Support (or OnCall…) as an example. When an issue arrives, it first goes through CustomerService
. If they can’t handle it, it is passed down to the next level, Supervisor
. If still unresolved, it continues down to TechSupport
. Additionally, different chains of responsibility can be formed for different issues. For example, problems from major clients may be handled directly starting from Supervisor
. The Responder Chain in Swift UIKit also uses the Chain of Responsibility pattern to respond to user actions on the UI.
WKWebView Scenario
In our WKWebView scenario, it is mainly applied in the func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void)
delegate method.
When the system receives a URL request, it passes through this method for us to decide whether to allow navigation, and calls
decisionHandler(.allow)
ordecisionHandler(.cancel)
at the end to indicate the result.
In the implementation of WKWebView, many conditions arise, or some pages need to be handled differently and bypassed:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// Original implementation...
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
guard let url = navigationAction.request.url else {
decisionHandler(.allow)
return
}
// Simulate business logic: WebViewController deeplinkCheck == true (means need to check and open page via DeepLinkManager)
if deeplinkCheck {
print("DeepLinkManager.open(\(url.absoluteString)")
// Simulate DeepLinkManager logic, if URL can be opened successfully, open it and end the process.
// if DeepLinkManager.open(url) == true {
decisionHandler(.cancel)
return
// }
}
// Simulate business logic: WebViewController isHomePage == true (means opening home page) & WebView is browsing home page, then switch TabBar Index
if isHomePage {
if url.absoluteString == "https://zhgchg.li" {
print("Switch UITabBarController to Index 0")
decisionHandler(.cancel)
}
}
// Simulate business logic: WebViewController noNeedNativePresent == false (means need to match special paths to open native pages)
if !noNeedNativePresent {
if url.pathComponents.count >= 3 {
if url.pathComponents[1] == "product" {
// match http://zhgchg.li/product/1234
let id = url.pathComponents[2]
print("Present ProductViewController(\(id)")
decisionHandler(.cancel)
} else if url.pathComponents[1] == "shop" {
// match http://zhgchg.li/shop/1234
let id = url.pathComponents[2]
print("Present ShopViewController(\(id)")
decisionHandler(.cancel)
}
// more...
}
}
// more...
decisionHandler(.allow)
}
As time goes by, features become more complex, and the logic here increases. If the processing order also needs to change, it will turn into a disaster.
NavigationActionHandler (Chain of Responsibility Pattern)
First, define the Handler Protocol:
public protocol NavigationActionHandler: AnyObject {
var nextHandler: NavigationActionHandler? { get set }
/// Handles navigation actions for the web view. Returns true if the action was handled, otherwise false.
func handle(webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) -> Bool
/// Executes the navigation action policy decision. If the current handler does not handle it, the next handler in the chain will be executed.
func exeute(webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void)
}
public extension NavigationActionHandler {
func exeute(webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
if !handle(webView: webView, decidePolicyFor: navigationAction, decisionHandler: decisionHandler) {
self.nextHandler?.exeute(webView: webView, decidePolicyFor: navigationAction, decisionHandler: decisionHandler) ?? decisionHandler(.allow)
}
}
}
The operation will be implemented in
func handle()
. Returntrue
if there are further processes, otherwise returnfalse
.func execute()
is the default chain access implementation. It traverses the entire operation chain from here. The default behavior is that whenfunc handle()
returnsfalse
(meaning this node cannot handle it), it automatically calls the nextnextHandler
’sexecute()
to continue processing until the end.
Implementation:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// Default implementation, usually placed at the end
public final class DefaultNavigationActionHandler: NavigationActionHandler {
public var nextHandler: NavigationActionHandler?
public init() {
}
public func handle(webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) -> Bool {
decisionHandler(.allow)
return true
}
}
//
final class PaymentNavigationActionHandler: NavigationActionHandler {
var nextHandler: NavigationActionHandler?
func handle(webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) -> Bool {
guard let url = navigationAction.request.url else {
return false
}
// Simulate business logic: Payment related, two-factor verification WebView...etc
print("Present Payment Verify View Controller")
decisionHandler(.cancel)
return true
}
}
//
final class DeeplinkManagerNavigationActionHandler: NavigationActionHandler {
var nextHandler: NavigationActionHandler?
func handle(webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) -> Bool {
guard let url = navigationAction.request.url else {
return false
}
// Simulate DeepLinkManager logic, if URL can be opened successfully, open it and end the process.
// if DeepLinkManager.open(url) == true {
decisionHandler(.cancel)
return true
// } else {
return false
//
}
}
// More...
Usage:
1
2
3
4
5
6
7
8
9
10
11
12
extension MyWKWebViewController: WKNavigationDelegate {
public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
let headNavigationActionHandler = DeeplinkManagerNavigationActionHandler()
let defaultNavigationActionHandler = DefaultNavigationActionHandler()
let paymentNavigationActionHandler = PaymentNavigationActionHandler()
headNavigationActionHandler.nextHandler = paymentNavigationActionHandler
paymentNavigationActionHandler.nextHandler = defaultNavigationActionHandler
headNavigationActionHandler.exeute(webView: webView, decidePolicyFor: navigationAction, decisionHandler: decisionHandler)
}
}
This way, when a request is received, it will be processed sequentially according to the handler chain we defined.
Combine with the previous Builder Pattern MyWKWebViewConfigurator
by making headNavigationActionHandler
a parameter, allowing external control over the WKWebView’s handling requirements and order:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
extension MyWKWebViewController: WKNavigationDelegate {
public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
configuration.headNavigationHandler?.exeute(webView: webView, decidePolicyFor: navigationAction, decisionHandler: decisionHandler) ?? decisionHandler(.allow)
}
}
//...
struct MyWKWebViewConfiguratorFactory {
enum ForType {
case `default`
case productPage
case payment
}
static func make(for type: ForType) -> MyWKWebViewConfigurator {
switch type {
case .default:
// Simulate default case with these handlers
let deplinkManagerNavigationActionHandler = DeeplinkManagerNavigationActionHandler()
let homePageTabSwitchNavigationActionHandler = HomePageTabSwitchNavigationActionHandler()
let nativeViewControllerNavigationActionHandlera = NativeViewControllerNavigationActionHandler()
let defaultNavigationActionHandler = DefaultNavigationActionHandler()
deplinkManagerNavigationActionHandler.nextHandler = homePageTabSwitchNavigationActionHandler
homePageTabSwitchNavigationActionHandler.nextHandler = nativeViewControllerNavigationActionHandlera
nativeViewControllerNavigationActionHandlera.nextHandler = defaultNavigationActionHandler
return MyWKWebViewConfigurator()
.add(scriptMessageStrategy: PageScriptMessageStrategy())
.add(scriptMessageStrategy: UserScriptMessageStrategy())
.set(headNavigationHandler: deplinkManagerNavigationActionHandler)
.set(overrideTitleFromWebView: false)
.set(disableZoom: false)
case .productPage:
return Self.make(for: .default).set(disableZoom: true).set(overrideTitleFromWebView: true)
case .payment:
// Simulate payment page only needs these handlers, with paymentNavigationActionHandler having highest priority
let paymentNavigationActionHandler = PaymentNavigationActionHandler()
let deplinkManagerNavigationActionHandler = DeeplinkManagerNavigationActionHandler()
let defaultNavigationActionHandler = DefaultNavigationActionHandler()
paymentNavigationActionHandler.nextHandler = deplinkManagerNavigationActionHandler
deplinkManagerNavigationActionHandler.nextHandler = defaultNavigationActionHandler
return MyWKWebViewConfigurator().set(headNavigationHandler: paymentNavigationActionHandler)
}
}
}
Strategy Pattern 策略模式
The Strategy Pattern is part of behavioral design patterns. It abstracts the actual operations, enabling the implementation of multiple operations that can be flexibly swapped according to different scenarios.
The above diagram uses different payment methods as examples. We abstract payment as a Payment
Protocol (Interface), then each payment method implements its own version. In PaymentContext
(simulating external use), the corresponding Payment instance is created based on the user’s chosen payment method, and pay()
is called uniformly to process the payment.
WKWebView Scenario
Used in interaction between WebView and frontend pages.
When the frontend JavaScript calls:
window.webkit.messageHandlers.Name.postMessage(Parameters);
It will enter WKWebView to find the corresponding
WKScriptMessageHandler
class with the givenName
and execute the operation.
The system already defines the Protocol and the corresponding func add(_ scriptMessageHandler: any WKScriptMessageHandler, name: String)
method. We only need to implement our own WKScriptMessageHandler
and add it to the WKWebView. The system will then dispatch to the appropriate concrete strategy based on the received name
, following the Strategy Pattern.
Here is a simple Protocol extension of WKScriptMessageHandler
with an additional identifier:String
for add(.. name:)
usage:
1
2
3
public protocol ScriptMessageStrategy: NSObject, WKScriptMessageHandler {
static var identifier: String { get }
}
Implementation:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
final class PageScriptMessageStrategy: NSObject, ScriptMessageStrategy {
static var identifier: String = "page"
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
// Simulate called from js: window.webkit.messageHandlers.page.postMessage("Close");
print("\(Self.identifier): \(message.body)")
}
}
//
final class UserScriptMessageStrategy: NSObject, ScriptMessageStrategy {
static var identifier: String = "user"
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
// Simulate called from js: window.webkit.messageHandlers.user.postMessage("Hello");
print("\(Self.identifier): \(message.body)")
}
}
WKWebView Registration Usage:
1
2
3
4
var scriptMessageStrategies: [ScriptMessageStrategy] = []
scriptMessageStrategies.forEach { scriptMessageStrategy in
webView.configuration.userContentController.add(scriptMessageStrategy, name: type(of: scriptMessageStrategy).identifier)
}
Combining with the previous Builder Pattern MyWKWebViewConfigurator
manages the registration of ScriptMessageStrategy
from outside:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public final class MyWKWebViewConfigurator {
//...
// You can encapsulate additional logic rules here
public func add(scriptMessageStrategy: ScriptMessageStrategy) -> Self {
// Here, the logic is to remove the old one first if there is a duplicate identifier
scriptMessageStrategies.removeAll(where: { type(of: $0).identifier == type(of: scriptMessageStrategy).identifier })
scriptMessageStrategies.append(scriptMessageStrategy)
return self
}
//...
}
//...
public class MyWKWebViewController: UIViewController {
//...
public override func viewDidLoad() {
super.viewDidLoad()
//...
configuration.scriptMessageStrategies.forEach { scriptMessageStrategy in
webView.configuration.userContentController.add(scriptMessageStrategy, name: type(of: scriptMessageStrategy).identifier)
}
//...
}
}
Question: Can this scenario also be implemented using the Chain of Responsibility Pattern?
At this point, some may ask if the Strategy Pattern here can be replaced by the Chain of Responsibility Pattern?
These two design patterns are both behavioral and can be interchangeable; however, the choice depends on the specific scenario. Here, it is a typical Strategy Pattern where WKWebView selects different strategies based on the Name. If the requirement involves chained dependencies or recovery relationships between strategies—for example, if AStrategy does not handle it and passes it to BStrategy—then the Chain of Responsibility Pattern should be considered.
Strategy vs. Chain of Responsibility
Strategy Pattern: There is a clear dispatch execution strategy, and the strategies are independent of each other.
Chain of Responsibility Pattern: The execution strategy is determined within each implementation. If it cannot handle the request, it passes it to the next implementation.
Complex scenarios can be achieved by applying the Chain of Responsibility Pattern within the Strategy Pattern.
Final Combination
Simple Factory Pattern
MyWKWebViewConfiguratorFactory
-> Encapsulates the creation process ofMyWKWebViewConfigurator
Builder Pattern
MyWKWebViewConfigurator
-> EncapsulatesMyWKWebViewConfiguration
parameters and build stepsMyWKWebViewConfiguration
Injection -> Used byMyWKWebViewController
Chain of Responsibility Pattern
MyWKWebViewController
’sfunc webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void)
-> callsheadNavigationHandler?.exeute(webView: webView, decidePolicyFor: navigationAction, decisionHandler: decisionHandler)
to execute the chain processingStrategy Pattern
MyWKWebViewController
useswebView.configuration.userContentController.addUserScript(XXX)
to dispatch corresponding JS Callers to the appropriate handling strategies
Complete Demo Repo
Further Reading
If you have any questions or feedback, feel free to contact me.
This post was originally published on Medium (View original post), and automatically converted and synced by ZMediumToMarkdown.