如何同步获取图片?SwiftUI异步图片加载问题求助
问题分析与解决方案
核心问题
- DOM加载时机不匹配:Instagram是单页应用,
webView(_:didFinish:)触发时,页面动态内容(如用户头像、用户名)还未渲染完成,导致固定选择器无法立刻获取元素,只能依赖重试,这是头像加载缓慢的根本原因。 - 请求逻辑分散:当前代码分开获取Reel缩略图和头像,多次调用
evaluateJavaScript,还重复发起重试请求,效率低下。
优化方案
- 合并JS脚本,一次性获取所有数据:编写单条JS脚本,同时提取Reel缩略图、用户头像URL、用户名、描述,减少JS评估次数。
- 等待动态DOM就绪:在JS内部实现递归检查逻辑,直到目标元素加载完成再返回结果,替代客户端固定间隔重试。
- 并行加载图片:利用URLSession任务组同时加载两张图片,确保内容同步更新到UI。
修改后的代码
替换原WebViewStore类的相关方法,整合所有逻辑:
class WebViewStore: NSObject, ObservableObject, WKNavigationDelegate { @Published var fetchedUIImage: UIImage? @Published var fetchedDescription: String? @Published var fetchedProfileUIImage: UIImage? @Published var fetchedUsername: String? private var webView: WKWebView override init() { let webConfiguration = WKWebViewConfiguration() self.webView = WKWebView(frame: .zero, configuration: webConfiguration) super.init() self.webView.navigationDelegate = self } func loadURL(url: URL) { let request = URLRequest(url: url) webView.load(request) } func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { fetchAllContent() } private func fetchAllContent() { // 内置元素检查逻辑的JS脚本,一次性获取所有数据 let fetchAllScript = """ (function waitForElements() { // 获取Reel缩略图 const ogImage = document.querySelector('meta[property="og:image"]')?.getAttribute('content'); // 获取用户头像 const profileImg = document.querySelector('header img.xpdipgo.x972fbf')?.getAttribute('src'); // 获取用户名 const username = document.querySelector('header h2.x1lliihq.x1plvlek.xryxfnj.x1n2onr6.x193iq5w.xeuugli.x1fj9vlw.x13faqbe.x1vvkbs.x1s928wv.xhkezso.x1gmr53x.x1cpjm7i.x1fgarty.x1943h6x.x1i0vuye.xvs91rp.x1s688f.x5n08af.x10wh9bi.x1wdrske.x8viiok.x18hxmgj')?.textContent; // 获取描述 const description = document.querySelector('div.x1iorvi4.x1pi30zi.x1l90r2v.x1swvt13')?.textContent; // 关键数据齐全则返回结果,否则延迟重试 if (ogImage && profileImg && username) { return { reelImageURL: ogImage, profileImageURL: profileImg, username: username, description: description || '' }; } return new Promise(resolve => setTimeout(() => resolve(waitForElements()), 100)); })(); """ webView.evaluateJavaScript(fetchAllScript) { [weak self] result, error in guard let self = self, error == nil, let data = result as? [String: Any] else { print("数据获取失败: \(error?.localizedDescription ?? "未知错误")") return } // 解析数据 guard let reelURLString = data["reelImageURL"] as? String, let reelURL = URL(string: reelURLString), let profileURLString = data["profileImageURL"] as? String, let profileURL = URL(string: profileURLString), let username = data["username"] as? String else { print("数据格式异常") return } let description = data["description"] as? String // 并行加载两张图片 Task { await withTaskGroup(of: (UIImage?, ReferenceWritableKeyPath<WebViewStore, UIImage?>.self).self) { group in group.addTask { let image = await self.loadImageAsync(from: reelURL) return (image, \.fetchedUIImage) } group.addTask { let image = await self.loadImageAsync(from: profileURL) return (image, \.fetchedProfileUIImage) } // 更新UI for await (image, keyPath) in group { if let image = image { DispatchQueue.main.async { self[keyPath: keyPath] = image } } } } // 更新用户名和描述 DispatchQueue.main.async { self.fetchedUsername = username self.fetchedDescription = description } } } } // 异步图片加载方法 private func loadImageAsync(from url: URL) async -> UIImage? { do { let (data, _) = try await URLSession.shared.data(from: url) return UIImage(data: data) } catch { print("图片加载失败: \(error.localizedDescription)") return nil } } }
关键优化点
- JS逻辑整合:通过JS内部递归检查元素,避免客户端发起多次重试请求,减少不必要的网络交互。
- 并行加载:利用Swift
TaskGroup同时加载两张图片,确保内容同步显示,解决加载不同步问题。 - 选择器适配:针对Instagram DOM结构调整用户名、描述的选择器(注意:Instagram的class可能会更新,需根据实际情况调整)。
内容的提问来源于stack exchange,提问作者P_n
相关产品推荐
相关产品推荐

