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
相关产品推荐
相关产品推荐

