DispatchGroup加载Rick and Morty API角色列表时滚动追加加载失效及EXC_BREAKPOINT错误解决咨询
DispatchGroup加载Rick and Morty API角色列表时滚动追加加载失效及EXC_BREAKPOINT错误解决咨询
嘿,我看你在用Rick and Morty API做剧集列表的时候,碰到了个头疼的问题——初始加载用DispatchGroup能正常拉取每个剧集的角色列表,但滚动触发追加加载就失效了,还出现了EXC_BREAKPOINT错误。我来帮你拆解下可能的原因和解决办法:
1. DispatchGroup的复用或生命周期问题
很多时候追加加载失效,是因为DispatchGroup没有正确初始化或者被重复使用了。比如你初始加载时创建了一个Group,但追加加载时没新建,而是复用了旧的,这会导致后续的enter/leave信号混乱,要么触发死锁,要么notify永远不执行。
解决思路:每次追加加载时都要新建一个DispatchGroup,确保每一批角色请求都有独立的分组管理。比如:
// 追加加载时的角色请求逻辑 let group = DispatchGroup() for url in newEpisode.characterUrls { group.enter() URLSession.shared.dataTask(with: URL(string: url)!) { data, _, error in defer { group.leave() } // 确保无论请求成功失败都能离开分组 // 处理角色数据解析... }.resume() } group.notify(queue: .main) { // 更新数据源+通知UI刷新 }
2. loadingMore标记的线程安全问题
你的loadingMore变量是用来防止重复触发加载的,但如果在多线程环境下直接修改(比如网络回调在后台线程,滚动触发在主线程),很容易出现竞态条件——比如多次触发加载,或者标记没正确更新导致后续加载被阻塞。
解决思路:给loadingMore加线程保护,用串行队列来处理读写操作:
private let loadingLockQueue = DispatchQueue(label: "com.rm.episode.loading.lock") private var _loadingMore = false private var loadingMore: Bool { get { loadingLockQueue.sync { _loadingMore } } set { loadingLockQueue.sync { _loadingMore = newValue } } }
3. EXC_BREAKPOINT错误的核心原因
这个错误大多是强制解包nil值或者数组越界导致的:
- 检查你处理角色URL时,有没有无效URL导致解析失败,后续模型数据为nil,然后在UI渲染时强制解包了;
- 检查通知delegate的
newIndexpath是否基于更新后的episodeViewModels数组生成,避免索引越界。
解决思路:所有可选类型都用if let/guard let做安全处理,比如角色解析时:
guard let character = try? JSONDecoder().decode(RMCharacter.self, from: data) else { return }
生成新索引路径时,确保是基于更新后的数组长度:
let startIndex = self.episodeViewModels.count - newViewModels.count let newIndexPaths = (startIndex..<self.episodeViewModels.count).map { IndexPath(row: $0, section: 0) }
4. 分页参数与Delegate回调时机问题
- 确认你正确使用了Rick and Morty API的分页参数:初始加载后要从
info?.next里获取下一页的请求地址,避免分页错误导致加载不到新数据; - 确保在DispatchGroup的
notify回调里先更新数据源,再通知UI刷新,而且必须在主线程执行,避免UI操作在后台线程触发异常。
修正后的追加加载逻辑示例
func fetchAdditionalEpisodes() { guard !loadingMore, let nextPageUrl = info?.next else { return } loadingMore = true URLSession.shared.dataTask(with: URL(string: nextPageUrl)!) { [weak self] data, _, error in guard let self = self, let data = data, error == nil else { self?.loadingMore = false return } do { let episodeResponse = try JSONDecoder().decode(RMEpisodeResponse.self, from: data) self.info = episodeResponse.info let newEpisodeViewModels = episodeResponse.results.map(RMEpisodeViewCellViewModel.init) let group = DispatchGroup() for vm in newEpisodeViewModels { for charUrl in vm.characterUrls { group.enter() URLSession.shared.dataTask(with: URL(string: charUrl)!) { charData, _, charError in defer { group.leave() } guard let charData = charData, charError == nil else { return } if let character = try? JSONDecoder().decode(RMCharacter.self, from: charData) { vm.addCharacter(character) } }.resume() } } group.notify(queue: .main) { self.loadingMore = false self.episodeViewModels.append(contentsOf: newEpisodeViewModels) let startIdx = self.episodeViewModels.count - newEpisodeViewModels.count let indexPaths = (startIdx..<self.episodeViewModels.count).map { IndexPath(row: $0, section: 0) } self.delegate?.didLoadAdditionalCharacters(with: indexPaths) } } catch { self.loadingMore = false print("剧集解析错误:\(error.localizedDescription)") } }.resume() }
备注:内容来源于stack exchange,提问作者Almat Kairatov
相关产品推荐
相关产品推荐

