How to implement normal loading of web content when I intercept all request header information using the Swizzling method

I am developing a web application, I hope to be able to intercept all requests in the loaded web page, match the network request I want to specifically intercept the network request and then obtain the request header information (not the user's personal privacy data interception), if the use is the method of injection, the request header information is incomplete, such as the more important "content-type" field in the request header, so I try to use Swizzling method to intercept all http and https requests, and I can now intercept all the request information. But there is still a problem with my project, when loading x.com, logging in, appearing white screen after the successful landing, and not loading to the page after the successful landing, I don't know where the reason is。 Below is my core code implementation fragment.

import WebKit
// MARK: - WebViewController
class WebViewController: UIViewController  {
    fileprivate var webView: WKWebView!
    private var schemeHandler: WWKProxyWKURLSchemeHandler!
    override func viewDidLoad() {
        super.viewDidLoad()
        Self.initSwizzling
        setupWebView()
        loadInitialContent()
    }
    private func setupWebView() {
        let config = WKWebViewConfiguration()
        schemeHandler = WWKProxyWKURLSchemeHandler()
        config.setURLSchemeHandler(self.schemeHandler, forURLScheme: "https")
        config.setURLSchemeHandler(self.schemeHandler, forURLScheme: "http")
        webView = WKWebView(frame: view.bounds, configuration: config)
        webView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        view.addSubview(webView)
    }
    private func loadInitialContent() {
        if let url = URL(string: "https://x.com") {
            let request = URLRequest(url: url,cachePolicy:.reloadIgnoringLocalCacheData,timeoutInterval: 15)
            webView.load(request)
        }
    } 
    func reloadWebView() {
        webView?.reloadFromOrigin()
    }
}
class WWKProxyWKURLSchemeHandler: NSObject, WKURLSchemeHandler {
    private let lock = NSLock()
    private var activeTasks = [ObjectIdentifier: URLSessionDataTask]()
    func webView(_ webView: WKWebView, start urlSchemeTask: WKURLSchemeTask) {
        lock.lock()
        defer { lock.unlock() }
        let taskID = ObjectIdentifier(urlSchemeTask)
        guard !activeTasks.keys.contains(taskID) else {
            print("⚠️ Task already started: \(urlSchemeTask.request.url?.absoluteString ?? "")")
            return
        }
        print("Intercepted URL---:",urlSchemeTask.request.url?.absoluteString ?? "")
        print("All requests intercepted----:",urlSchemeTask.request.allHTTPHeaderFields)
        let request = urlSchemeTask.request
        let dataTask = URLSession.shared.dataTask(with: request) { [weak self] data, response, error in
            guard let self = self else { return }
            self.lock.lock()
            let isActive = self.activeTasks[taskID] != nil
            self.lock.unlock()
            guard isActive else {
                print("🌀 Task already cancelled: \(urlSchemeTask.request.url?.absoluteString ?? "")")
                return
            }
            // Make sure it only handles once
            guard self.activeTasks.keys.contains(taskID) else { return }
            self.activeTasks.removeValue(forKey: taskID)
            DispatchQueue.main.async {
                if let error = error {
                    urlSchemeTask.didFailWithError(error)
                    print("🔴 Task failed: \(urlSchemeTask.request.url?.absoluteString ?? "") - \(error.localizedDescription)")
                    return
                }
                guard let response = response, let data = data else {
                    urlSchemeTask.didFailWithError(URLError(.unknown))
                    print("🔴 Invalid response: \(urlSchemeTask.request.url?.absoluteString ?? "")")
                    return
                }
                urlSchemeTask.didReceive(response)
                urlSchemeTask.didReceive(data)
                urlSchemeTask.didFinish()
                print("🟢 Task completed: \(urlSchemeTask.request.url?.absoluteString ?? "")")
            }
        }
        self.activeTasks[taskID] = dataTask
        dataTask.resume()
    }
    func webView(_ webView: WKWebView, stop task: WKURLSchemeTask) {}
    private func finishTask(taskID: ObjectIdentifier ,task: WKURLSchemeTask, response: URLResponse?, data: Data?, error: Error?) {
            lock.lock()
            defer { lock.unlock() }
            guard activeTasks.keys.contains(taskID) else { return }
            activeTasks.removeValue(forKey: taskID)
            DispatchQueue.main.async {
                if let error = error {
                    task.didFailWithError(error)
                    print("🔴 Task failed: \(task.request.url?.absoluteString ?? "") - \(error.localizedDescription)")return}
                guard let response = response, let data = data else {
                    task.didFailWithError(URLError(.unknown))
                    print("🔴 Invalid response: \(task.request.url?.absoluteString ?? "")") return }
                task.didReceive(response)
                task.didReceive(data)
                task.didFinish()
                print("🟢 Task completed: \(task.request.url?.absoluteString ?? "")")
           }
        }
}
extension WKWebView {
    @objc
    dynamic class func qm_handlesURLScheme(_ urlScheme: String) -> Bool {
        if urlScheme == "https" || urlScheme == "http" {return false}
        return self.qm_handlesURLScheme(urlScheme)
    }
    // Exchange of execution methods
    static func setupSwizzling() {
        let originalSelector = #selector(handlesURLScheme(_:))
        let swizzledSelector = #selector(qm_handlesURLScheme(_:))
        // Implemented by Runtime Get Method
        guard
            let originalMethod = class_getClassMethod(WKWebView.self, originalSelector),
            let swizzledMethod = class_getClassMethod(WKWebView.self, swizzledSelector)
        else {
            return
        }
        // Implementation of Exchange Methods
        method_exchangeImplementations(originalMethod, swizzledMethod)
    }
}
extension WebViewController {
    private static let initSwizzling: Void = {
        DispatchQueue.once(token: "com.webview.swizzling") {
            WKWebView.setupSwizzling()
        }
    }()
}
// GCD Once 
extension DispatchQueue {
    private static var tokens = Set<String>()
    class func once(token: String, block: () -> Void) {
        objc_sync_enter(self)
        defer { objc_sync_exit(self) }
        guard !tokens.contains(token) else { return }
        tokens.insert(token)
        block()
    }
}
extension WebViewController: WKNavigationDelegate {
    func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
        decisionHandler(.allow)
    }   
}
Answered by DTS Engineer in 833838022

Swizzling is a bad idea in general, and it’s not something we support when it comes to Apple types [1]. Moreover, in this case it won’t work reliably because WKWebView does all of its networking in a separate process, and your swizzles don’t apply there.

If you want to intercept all the request made by a WKWebView, your best option is to implement a custom proxy and then apply that proxy to the web view using the proxyConfigurations property.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] If you search the forums you’ll find lots of threads where folks have hit weird compatibility problems because they’re swizzling methods on Apple types, like here and here.

Swizzling is a bad idea in general, and it’s not something we support when it comes to Apple types [1]. Moreover, in this case it won’t work reliably because WKWebView does all of its networking in a separate process, and your swizzles don’t apply there.

If you want to intercept all the request made by a WKWebView, your best option is to implement a custom proxy and then apply that proxy to the web view using the proxyConfigurations property.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] If you search the forums you’ll find lots of threads where folks have hit weird compatibility problems because they’re swizzling methods on Apple types, like here and here.

How to implement normal loading of web content when I intercept all request header information using the Swizzling method
 
 
Q