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

Swift ViewController代理异步方法:NSOutlineView异步加载最佳模式

处理NSOutlineView异步数据源的最佳实践

在outlineView(_:viewFor:item:)方法中直接发起异步请求并等待数据是错误的——这个方法要求同步返回视图,强行等待会阻塞UI线程,直接返回空则会导致布局异常。正确的处理模式是「先返回占位视图,异步加载完成后更新视图或刷新行」,具体实现如下:

1. 同步返回占位视图

代理方法中优先返回一个占位视图(比如带加载指示器的单元格),同时发起异步数据请求,避免阻塞UI:

func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? {
    // 复用或创建占位单元格
    let placeholderID = NSUserInterfaceItemIdentifier("PlaceholderCell")
    let placeholderCell = outlineView.makeView(withIdentifier: placeholderID, owner: self) as? LoadingCell ?? LoadingCell()
    placeholderCell.startLoadingAnimation()

    // 发起异步数据请求
    loadAsyncData(for: item) { [weak self, weak outlineView] data in
        guard let self = self, let outlineView = outlineView else { return }
        self.handleLoadedData(data: data, for: item, in: outlineView, column: tableColumn)
    }

    return placeholderCell
}

2. 异步完成后更新视图或刷新行

数据加载完成后,需要区分两种场景:

  • 目标item对应的视图仍在可见范围内:直接更新视图内容
  • 视图已被复用(滚动出屏幕):更新本地数据源缓存,然后刷新对应行
private func handleLoadedData(data: DataModel, for item: Any, in outlineView: NSOutlineView, column: NSTableColumn?) {
    // 1. 更新本地数据源缓存(必须步骤,保证数据一致性)
    updateLocalDataSource(with: data, for: item)

    // 2. 查找item对应的行索引
    guard let row = outlineView.row(forItem: item) else { return }
    let columnIndex = column?.index ?? 0

    // 3. 尝试获取当前行的视图(如果仍在可见范围)
    if let targetCell = outlineView.view(atColumn: columnIndex, row: row, makeIfNecessary: false) as? CustomDataCell {
        targetCell.configure(with: data)
    } else {
        // 视图已被复用,直接刷新对应行
        outlineView.reloadData(forRowIndexes: IndexSet(integer: row), columnIndexes: IndexSet(integer: columnIndex))
    }
}

3. 避免重复请求与资源浪费

  • 维护一个加载状态集合,标记正在请求的item,防止同一item被多次触发请求
  • 对于滚动出屏幕的item,可以取消其未完成的异步请求,减少无效资源消耗
// 记录正在加载的item(要求Item实现AnyHashable)
private var activeLoadingItems = Set<AnyHashable>()
private var pendingTasks: [AnyHashable: URLSessionDataTask] = [:]

private func loadAsyncData(for item: Any, completion: @escaping (DataModel) -> Void) {
    guard let hashableItem = item as? AnyHashable, !activeLoadingItems.contains(hashableItem) else {
        completion(DataModel.empty)
        return
    }
    activeLoadingItems.insert(hashableItem)

    // 示例:异步网络请求
    let task = URLSession.shared.dataTask(with: dataURL(for: item)) { [weak self] data, _, error in
        defer { 
            self?.activeLoadingItems.remove(hashableItem)
            self?.pendingTasks.removeValue(forKey: hashableItem)
        }
        let result = self?.parseData(data) ?? DataModel.empty
        completion(result)
    }
    task.resume()

    // 绑定任务与item,后续可取消未完成请求
    pendingTasks[hashableItem] = task
}

4. 优化用户体验

  • 占位视图添加加载动画,明确告知用户加载状态
  • 处理请求失败场景:显示错误提示或重试按钮
  • 快速滚动时,取消离开可视区域的item的请求,降低资源占用

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 19:25:41