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

SwiftUI配合Core Data使用时如何遍历递归对象图且避免重复展示

解决方案

核心问题:将去重的副作用逻辑放在SwiftUI视图的body计算流程中会导致结果异常,SwiftUI会因状态变化、布局刷新等场景多次重新执行body代码,多次遍历对象图,外部维护的可变数组会被反复写入,无法得到预期去重效果。

方案1:提前在数据层完成去重(最推荐)

直接把遍历去重逻辑和视图渲染完全拆分,先得到无重复的人员列表,再交给视图渲染,视图仅负责静态展示,不需要处理递归逻辑。
首先给Person添加遍历方法,这里用广度优先遍历实现无重复采集:

// 首先确保Person类实现Hashable协议,Core Data生成的子类默认按对象地址哈希,如需按业务ID/姓名去重可自行重写Hashable逻辑
extension Person {
    func fetchAllUniqueFriends() -> [Person] {
        var visitedSet = Set<Person>()
        var resultList = [Person]()
        var traverseQueue = [self]
        
        while !traverseQueue.isEmpty {
            let currentPerson = traverseQueue.removeFirst()
            guard !visitedSet.contains(currentPerson) else { continue }
            visitedSet.insert(currentPerson)
            resultList.append(currentPerson)
            traverseQueue.append(contentsOf: currentPerson.friends)
        }
        // 移除首位的自己,仅保留好友列表
        return Array(resultList.dropFirst())
    }
}

视图直接使用处理好的无重复列表渲染即可:

struct FriendsListView: View {
    let rootPerson: Person
    
    var body: some View {
        VStack(alignment: .leading, spacing: 4) {
            Text(rootPerson.name)
                .font(.headline)
            ForEach(rootPerson.fetchAllUniqueFriends(), id: \.self) { friend in
                Text(friend.name)
                    .padding(.leading, 12)
            }
        }
    }
}

方案2:保留递归视图写法,把访问集合作为参数传递

如果需要保留递归视图的层级展示效果,可以把已访问的人员集合作为视图的初始化参数传递,每次递归生成新的集合,完全避免外部可变状态的影响:

struct RecursiveFriendsView: View {
    private let currentPerson: Person
    private let visitedPersons: Set<Person>
    
    // 根节点初始化方法
    init(root: Person) {
        self.currentPerson = root
        self.visitedPersons = [root]
    }
    
    // 递归调用的内部初始化方法
    private init(current: Person, visited: Set<Person>) {
        self.currentPerson = current
        self.visitedPersons = visited
    }
    
    var body: some View {
        VStack(alignment: .leading, spacing: 4) {
            Text(currentPerson.name)
            ForEach(currentPerson.friends.filter { !visitedPersons.contains($0) }, id: \.self) { friend in
                RecursiveFriendsView(
                    current: friend,
                    visited: visitedPersons.union([friend])
                )
                .padding(.leading, 16)
            }
        }
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.04 01:15:01