显著提高iOS应用中Web页面的加载速度 - 提前下载页面的关键资源(如JavaScript、CSS和图像)

在这里插入图片描述

手动下载并缓存资源是一种有效的方式,可以确保在需要时资源已经在本地存储,这样可以显著提高加载速度。

缓存整个 web 页面的所有资源文件

具体实现步骤

  1. 下载和缓存资源:包括 HTML 文件、CSS、JavaScript 和图像。
  2. 在应用启动时预加载资源
  3. 构建包含所有预加载资源的 HTML 字符串
  4. 加载构建的 HTML 字符串到 WKWebView

资源下载和缓存管理类

import Foundation

class ResourceDownloader {
    static let shared = ResourceDownloader()
    private init() {}
    
    func downloadResources() {
        let resources = [
            URL(string: "https://www.example.com/styles.css")!,
            URL(string: "https://www.example.com/script.js")!,
            URL(string: "https://www.example.com/image.png")!,
            URL(string: "https://www.example.com/index.html")!
        ]
        
        for resource in resources {
            downloadResource(from: resource)
        }
    }
    
    private func downloadResource(from url: URL) {
        let task = URLSession.shared.downloadTask(with: url) { localURL, response, error in
            guard let localURL = localURL else { return }
            
            do {
                let data = try Data(contentsOf: localURL)
                self.cacheResource(data: data, url: url)
            } catch {
                print("Failed to load resource: \(error)")
            }
        }
        task.resume()
    }
    
    private func cacheResource(data: Data, url: URL) {
        let cacheDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
        let fileURL = cacheDirectory.appendingPathComponent(url.lastPathComponent)
        
        do {
            try data.write(to: fileURL)
            print("Resource cached: \(fileURL)")
        } catch {
            print("Failed to cache resource: \(error)")
        }
    }
    
    func getCachedResource(for url: URL) -> Data? {
        let cacheDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
        let fileURL = cacheDirectory.appendingPathComponent(url.lastPathComponent)
        
        return try? Data(contentsOf: fileURL)
    }
}

在应用启动时预加载资源

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // 预加载资源
        ResourceDownloader.shared.downloadResources()
        return true
    }
}

使用缓存的资源加载完整页面

import UIKit
import WebKit

class ViewController: UIViewController {
    
    var webView: WKWebView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        webView = WKWebView(frame: self.view.bounds)
        self.view.addSubview(webView)
        
        // 构建完整的HTML内容
        if let htmlURL = URL(string: "https://www.example.com/index.html"),
           let cachedHTML = ResourceDownloader.shared.getCachedResource(for: htmlURL),
           let htmlString = String(data: cachedHTML, encoding: .utf8) {
            
            let completeHTMLString = embedCachedResources(in: htmlString)
            
            // 加载HTML内容到webView
            webView.loadHTMLString(completeHTMLString, baseURL: nil)
        } else {
            // 如果没有缓存,则加载远程 URL
            let request = URLRequest(url: URL(string: "https://www.example.com")!)
            webView.load(request)
        }
    }
    
    private func embedCachedResources(in htmlString: String) -> String {
        var modifiedHTMLString = htmlString
        
        // 嵌入预加载的CSS
        if let cssURL = URL(string: "https://www.example.com/styles.css"),
           let cachedCSS = ResourceDownloader.shared.getCachedResource(for: cssURL),
           let cssString = String(data: cachedCSS, encoding: .utf8) {
            let cssTag = "<style>\(cssString)</style>"
            modifiedHTMLString = modifiedHTMLString.replacingOccurrences(of: "<link rel=\"stylesheet\" href=\"styles.css\">", with: cssTag)
        }
        
        // 嵌入预加载的JavaScript
        if let jsURL = URL(string: "https://www.example.com/script.js"),
           let cachedJS = ResourceDownloader.shared.getCachedResource(for: jsURL),
           let jsString = String(data: cachedJS, encoding: .utf8) {
            let jsTag = "<script>\(jsString)</script>"
            modifiedHTMLString = modifiedHTMLString.replacingOccurrences(of: "<script src=\"script.js\" defer></script>", with: jsTag)
        }
        
        // 嵌入预加载的图像
        if let imageURL = URL(string: "https://www.example.com/image.png"),
           let cachedImage = ResourceDownloader.shared.getCachedResource(for: imageURL) {
            let base64Image = cachedImage.base64EncodedString()
            let imgTag = "<img src='data:image/png;base64,\(base64Image)' alt='Preloaded Image'>"
            modifiedHTMLString = modifiedHTMLString.replacingOccurrences(of: "<img src=\"image.png\" alt=\"Preloaded Image\">", with: imgTag)
        }
        
        return modifiedHTMLString
    }
}

详细说明

  1. 资源下载和缓存

    • 使用 URLSession 下载资源(包括HTML文件、CSS、JavaScript和图像),并将其缓存到本地存储。
    • 下载完成后,资源数据被写入本地存储,以便后续使用。
  2. 预加载资源

    • 在应用启动时调用 downloadResources() 方法,预先下载和缓存所需的资源。
  3. 使用缓存的资源加载完整页面

    • viewDidLoad() 方法中,通过 getCachedResource(for:) 获取缓存的HTML内容。
    • 使用 embedCachedResources(in:) 方法,将预加载的 CSS、JavaScript 和图像嵌入到 HTML 内容中。
    • 使用 webView.loadHTMLString(completeHTMLString, baseURL: nil) 加载修改后的 HTML 内容。

通过这种方式,可以确保在加载页面时直接使用本地缓存的资源,从而显著提高页面加载速度,提供更好的用户体验。


只缓存 CSS、JavaScript 和图像

只有 CSS、JavaScript 和图像是预加载的,而 HTML 文件是在打开 WKWebView 时才开始下载的。这种策略在某些情况下可能更加高效,尤其是当 HTML 文件需要动态生成或者频繁更新时。

为什么预加载 CSS、JavaScript 和图像?

预加载 CSS、JavaScript 和图像有几个优点:

  • 减少首次渲染时间:通过提前加载关键资源,可以显著减少页面首次渲染的时间,提高用户体验。
  • 减轻服务器压力:本地缓存的资源可以减少重复请求,减轻服务器的负载。
  • 提高离线体验:在某些情况下,即使没有网络连接,用户也能看到页面的一部分内容。

什么时候 HTML 文件在打开 WKWebView 时下载?

  • 动态内容:如果 HTML 文件内容是动态生成的(例如,包含个性化数据或实时更新的数据),那么在每次加载页面时请求最新的 HTML 文件是必要的。
  • 频繁更新:如果 HTML 文件内容频繁更新,预加载 HTML 文件可能会导致内容不一致。
  • 用户特定数据:某些应用需要根据用户的身份或状态生成不同的 HTML 内容。

完整的 Swift 代码实现

展示如何预加载 CSS、JavaScript 和图像,并在打开 WKWebView 时下载 HTML 文件。

import UIKit
import WebKit

class ViewController: UIViewController {
    
    var webView: WKWebView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 创建并配置 WKWebView
        let configuration = WKWebViewConfiguration()
        let urlSchemeHandler = LocalResourceHandler()
        configuration.setURLSchemeHandler(urlSchemeHandler, forURLScheme: "local")
        
        webView = WKWebView(frame: self.view.bounds, configuration: configuration)
        self.view.addSubview(webView)
        
        // 加载远程 HTML 文件
        if let htmlURL = URL(string: "https://www.example.com/index.html") {
            let request = URLRequest(url: htmlURL)
            webView.load(request)
        }
    }
}

class LocalResourceHandler: NSObject, WKURLSchemeHandler {
    
    func webView(_ webView: WKWebView, start urlSchemeTask: WKURLSchemeTask) {
        guard let url = urlSchemeTask.request.url else {
            return
        }
        
        // 仅处理特定的 URL 方案,例如 "local"
        guard url.scheme == "local" else {
            // 对于非 "local" 方案的请求,不处理
            return
        }
        
        if let data = ResourceDownloader.shared.getCachedResource(for: url) {
            let mimeType = determineMimeType(for: url)
            let response = URLResponse(url: url, mimeType: mimeType, expectedContentLength: data.count, textEncodingName: nil)
            urlSchemeTask.didReceive(response)
            urlSchemeTask.didReceive(data)
            urlSchemeTask.didFinish()
        } else {
            // 缓存资源不可用,发起网络请求
            downloadResource(from: url, urlSchemeTask: urlSchemeTask)
        }
    }
    
    func webView(_ webView: WKWebView, stop urlSchemeTask: WKURLSchemeTask) {}
    
    private func determineMimeType(for url: URL) -> String {
        switch url.pathExtension {
        case "css":
            return "text/css"
        case "js":
            return "application/javascript"
        case "png":
            return "image/png"
        default:
            return "text/plain"
        }
    }
    
    private func downloadResource(from url: URL, urlSchemeTask: WKURLSchemeTask) {
        let task = URLSession.shared.dataTask(with: url) { data, response, error in
            if let error = error {
                urlSchemeTask.didFailWithError(error)
                return
            }
            
            guard let data = data, let response = response else {
                urlSchemeTask.didFailWithError(NSError(domain: "LocalResourceHandler", code: 404, userInfo: nil))
                return
            }
            
            // 缓存资源
            ResourceDownloader.shared.cacheResource(data: data, url: url)
            
            // 返回资源
            urlSchemeTask.didReceive(response)
            urlSchemeTask.didReceive(data)
            urlSchemeTask.didFinish()
        }
        task.resume()
    }
}

class ResourceDownloader {
    static let shared = ResourceDownloader()
    private init() {}
    
    func downloadResources() {
        let resources = [
            URL(string: "https://www.example.com/styles.css")!,
            URL(string: "https://www.example.com/script.js")!,
            URL(string: "https://www.example.com/image.png")!
        ]
        
        for resource in resources {
            downloadResource(from: resource)
        }
    }
    
    private func downloadResource(from url: URL) {
        let task = URLSession.shared.downloadTask(with: url) { localURL, response, error in
            guard let localURL = localURL else { return }
            
            do {
                let data = try Data(contentsOf: localURL)
                self.cacheResource(data: data, url: url)
            } catch {
                print("Failed to load resource: \(error)")
            }
        }
        task.resume()
    }
    
    func cacheResource(data: Data, url: URL) {
        let cacheDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
        let fileURL = cacheDirectory.appendingPathComponent(url.lastPathComponent)
        
        do {
            try data.write(to: fileURL)
            print("Resource cached: \(fileURL)")
        } catch {
            print("Failed to cache resource: \(error)")
        }
    }
    
    func getCachedResource(for url: URL) -> Data? {
        let cacheDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!
        let fileURL = cacheDirectory.appendingPathComponent(url.lastPathComponent)
        
        return try? Data(contentsOf: fileURL)
    }
}
在应用启动时预加载资源
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // 预加载资源
        ResourceDownloader.shared.downloadResources()
        return true
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/710126.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

鸿蒙 游戏来了 鸿蒙版 五子棋来了 我不允许你不会

团队介绍 作者:徐庆 团队:坚果派 公众号:“大前端之旅” 润开鸿生态技术专家,华为HDE,CSDN博客专家,CSDN超级个体,CSDN特邀嘉宾,InfoQ签约作者,OpenHarmony布道师,电子发烧友专家博客,51CTO博客专家,擅长HarmonyOS/OpenHarmony应用开发、熟悉服务卡片开发。欢迎合…

【复旦邱锡鹏教授《神经网络与深度学习公开课》笔记】梯度的反向传播算法

矩阵微积分&#xff08;Matrix Calculus&#xff09; 在开始之前&#xff0c;需要先了解矩阵微积分的一些计算规则。 首先&#xff0c;对于矩阵微积分的表示&#xff0c;通常由两种符号约定&#xff1a; 分母布局 标量关于向量的导数为列向量 向量关于标量的导数为行向量 N维…

如何应对pcdn的流量攻击?

面对PCDN的流量攻击&#xff0c;可以采取以下措施来应对&#xff1a; 一&#xff0e;配置防火墙&#xff1a; 1.禁止未授权的PCDN域名访问&#xff1a;根据网络需求&#xff0c;配置防火墙规则&#xff0c;只允许特定的PCDN域名进行访问&#xff0c;从而防止未经授权的PCDN节…

shell编程基础(第16篇:命令是什么?有哪些注意事项)

前言 前面我们已经使用过各种各样的命令&#xff0c;那么命令到底是什么呢&#xff1f;我们又该怎么理解该术语&#xff1f; 什么是命令&#xff1f; 命令是command的中文翻译&#xff0c;能在命令行中执行的是命令。因为早期的计算机只有文字界面&#xff0c;命令是程序&#…

【Kafka】Kafka生产者-04

【Kafka】Kafka生产者-04 1. 生产者发送消息流程1.1 发送原理 2. 相关文档 1. 生产者发送消息流程 1.1 发送原理 在消息发送的过程中&#xff0c;涉及到了两个线程——main 线程和 Sender 线程。 在 main 线程中创建了一个双端队列 RecordAccumulator。 main 线程将消息发送给…

CSS实现经典打字小游戏《生死时速》

&#x1f33b; 前言 CSS 中有这样一个模块&#xff1a;Motion Path 运动模块&#xff0c;它可以使元素按照自定义的路径进行移动。本文将为你讲解这个模块属性的使用&#xff0c;并且利用它实现我小时候电脑课经常玩的一个打字游戏&#xff1a;金山打字的《生死时速》。 &…

【免费Web系列】大家好 ,今天是Web课程的第二一天点赞收藏关注,持续更新作品 !

这是Web第一天的课程大家可以传送过去学习 http://t.csdnimg.cn/K547r 员工管理 1. 条件分页查询 1.1 概述 在页面原型中&#xff0c;我们可以看到在查询员工信息列表时&#xff0c;既需要根据条件动态查询&#xff0c;还需要对查询的结果进行分页处理。 那要完成这个页面…

计算机组成原理历年考研真题对应知识点(计算机系统层次结构)

目录 1.2计算机系统层次结构 1.2.2计算机硬件 【命题追踪——冯诺依曼计算机的特点(2019)】 【命题追踪——MAR 和 MDR 位数的概念和计算(2010、2011)】 1.2.3计算机软件 【命题追踪——三种机器语言的特点(2015)】 【命题追踪——各种翻译程序的概念(2016)】 1.2.5计算…

四十五、openlayers官网示例Icon modification解析——在地图上添加标记图形并随意移动它的位置

官网demo地址&#xff1a; Icon modification 这篇讲了如何随意移动地图上的矢量点。 先在地图上添加一个矢量点&#xff0c;其中anchorXUnits 和 anchorYUnits: 指定锚点的单位。fraction 表示相对于图标的宽度&#xff08;0到1之间&#xff09;&#xff0c;pixels 表示以像素…

关于Unity四种合批技术详解

文章目录 一.静态合批(StaticBatching)1.启用静态合批2.举例说明3.静态合批的限制4.静态合批的优点缺点5.动态指定物品合批 二.动态合批(Dynamic Batching)1.启用动态合批2.合批规则3.举例说明4.使用限制 三.GPU Instancing1.启用GPU Instancing2.启用限制3.举例说明 四.SRP Ba…

【面试干货】ArrayList、Vector、LinkedList的存储性能和特性比较

【面试干货】ArrayList、Vector、LinkedList的存储性能和特性比较 1、ArrayList1.1 存储性能1.2 特性1.3 示例用法 2、Vector2.1 存储性能2.2 特性2.3 示例用法 3、LinkedList3.1 存储性能3.2 特性3.3 示例用法 4、ArrayList、Vector、LinkedList用法总结 &#x1f496;The Beg…

Java数据库编程

引言 在现代应用开发中&#xff0c;与数据库交互是不可或缺的一部分。Java提供了JDBC&#xff08;Java Database Connectivity&#xff09; API&#xff0c;允许开发者方便地连接到数据库并执行SQL操作。本文将详细介绍Java数据库编程的基础知识&#xff0c;包括JDBC的基本概念…

AI金融投资:批量下载深交所公募REITs公开说明书

打开深交所公募REITs公开说明书页面&#xff0c;F12查看网络&#xff0c;找到真实地址&#xff1a;https://reits.szse.cn/api/disc/announcement/annList?random0.3555675437003616 { "announceCount": 39, "data": [ { "id": "80bc9…

循环订单激励:打造企业增长新引擎

循环订单激励&#xff1a;打造企业增长新引擎 在当今竞争激烈的商业环境中&#xff0c;许多企业都在寻求独特而高效的营销策略以吸引并留住客户。今天&#xff0c;我要为您介绍的是一种名为“循环订单激励”的新颖模式&#xff0c;它不仅能提升客户参与度&#xff0c;还能为企…

《站在2024年的十字路口:计算机专业是否仍是高考生的明智之选?》

文章目录 每日一句正能量前言行业竞争现状行业饱和度和竞争激烈程度[^3^]新兴技术的影响[^3^]人才需求的变化[^3^]行业创新动态如何保持竞争力 专业与个人的匹配度判断专业所需的技术能力专业核心课程对学生的要求个人兴趣和性格特点专业对口的职业发展要求实践和经验个人价值观…

vivado HW_VIO

描述 虚拟输入/输出&#xff08;VIO&#xff09;调试核心hw_VIO可以监视和驱动内部 在编程的XilinxFPGA上实时显示信号。在没有物理访问的情况下 目标硬件&#xff0c;可以使用此调试功能来驱动和监视 存在于物理设备上。 VIO核心具有硬件探测器hw_probe对象&#xff0c;用于监…

VS2022,编译最新版obs30.1

VS2022&#xff0c;编译最新版obs30.1 VS2022&#xff0c;编译最新版obs30.1 VS2022&#xff0c;编译最新版obs30.1一、源码编译1.1 官方编译1.2 利用cmake软件进行编译 二、为二次开发做准备遇到问题&#xff0c;暂时无法解决 一、源码编译 编译环境Win11&#xff0c;VS2022&…

C++语法08 数据类型之间的强制转换

目录 强制类型转换 强制类型转换格式 整型转换成浮点型 整型转换成浮点型其他写法 训练&#xff1a;糖果奖励 糖果奖励参考代码 浮点型转换成整型 浮点型转换成整型其他写法 训练&#xff1a;分离小数 分离小数参考代码 强制类型转换 强制类型转换&#xff0c;就是把…

C++ static关键字详解

背景 前段时间初步整理了C中static的相关知识点&#xff0c;以此做个记录。 在C中&#xff0c;static关键字是常见的修饰符。从大方向上static分为两类&#xff1a; 1.类或结构体外的static 2.类或结构体内的static 因此&#xff0c;本文内容的划分如下&#xff1a; 接下来会…