You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何同步获取图片?SwiftUI异步图片加载问题求助

问题分析与解决方案

核心问题

  1. DOM加载时机不匹配:Instagram是单页应用,webView(_:didFinish:)触发时,页面动态内容(如用户头像、用户名)还未渲染完成,导致固定选择器无法立刻获取元素,只能依赖重试,这是头像加载缓慢的根本原因。
  2. 请求逻辑分散:当前代码分开获取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
        }
    }
}

关键优化点

  1. JS逻辑整合:通过JS内部递归检查元素,避免客户端发起多次重试请求,减少不必要的网络交互。
  2. 并行加载:利用Swift TaskGroup同时加载两张图片,确保内容同步显示,解决加载不同步问题。
  3. 选择器适配:针对Instagram DOM结构调整用户名、描述的选择器(注意:Instagram的class可能会更新,需根据实际情况调整)。

内容的提问来源于stack exchange,提问作者P_n

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 13:24:52