iOS开发:如何按队列顺序依次获取SEO查询API的返回结果
实现串行SEO关键词请求的解决方案
问题根源
你当前的逻辑中直接用forEach遍历触发所有关键词的请求,而URLSession.dataTask是异步操作,所有请求会同时发出,回调返回顺序由网络响应速度决定,因此会出现结果顺序混乱的问题。要实现按顺序请求,需要等待上一个关键词的全部请求(包括翻页逻辑)完全结束后,再触发下一个关键词的请求。
代码修改步骤
1. 新增异步回调定义,传递请求完成状态
在你的SEO请求工具类中新增回调类型,用于标记单个关键词的全部请求(含翻页)是否结束:
typealias SEOFetchCompletion = () -> Void
2. 修改请求链,透传完成回调
修改fetchSEO方法,新增回调参数
func fetchSEO(keyword: String, requestURL: String? = nil, start: Int? = 1, completion: @escaping SEOFetchCompletion) { let start = start let urlString = "https://www.googleapis.com/customsearch/v1?q=\(keyword)&lr=\(lang)&gl=\(location)&start=\(start ?? 1)&key=\(apiKey)&cx=\(searchEngine)" performRequest(with: urlString, for: keyword, request: requestURL, startIndex: start ?? 1, completion: completion) }
修改performRequest方法,所有结束分支触发回调
func performRequest(with urlString: String, for keyword: String, request requestURL: String?, startIndex: Int, completion: @escaping SEOFetchCompletion) { guard let url = URL(string: urlString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!) else { completion() // URL无效直接触发回调 return } let session = URLSession(configuration: .default) let dataTask = session.dataTask(with: url) { data, response, error in if let e = error { self.delegate?.didFailWithError(error: e) completion() // 请求失败触发回调 return } guard let dataSafe = data, let seoData = self.parseJSON(data: dataSafe) else { completion() // 数据解析失败触发回调 return } self.checkForLinkorNextPage(seoData: seoData, url: urlString, requestURL: requestURL, keyword: keyword, nextPageStartIndex: startIndex, completion: completion) } dataTask.resume() }
修改checkForLinkorNextPage方法,所有结束分支触发回调
func checkForLinkorNextPage(seoData: SEODataBrain, url: String, requestURL: String?, keyword: String, nextPageStartIndex: Int, completion: @escaping SEOFetchCompletion) { var n = 0 var startIndexForNextPage = nextPageStartIndex while n < seoData.items.count { guard let link = seoData.items[n].link else { n += 1 continue } delegate?.getLinks(link: link) let listLine = startIndexForNextPage + n if let request = requestURL, link.contains(request) { delegate?.seoModel(link: link, url: request, listLine: listLine, keyword: keyword) completion() // 找到匹配链接,触发回调 return } n += 1 if n == 10 { startIndexForNextPage += 10 // 翻页请求透传同一个回调,保证所有翻页结束后才触发下一个关键词请求 fetchSEO(keyword: keyword, requestURL: requestURL, start: startIndexForNextPage, completion: completion) return } } // 无匹配链接也无下一页,触发回调 completion() }
3. 替换原循环逻辑,用递归实现串行请求
修改refresh方法中的遍历逻辑:
@objc func refresh(_ sender: AnyObject) { if keywordModel.keywordNames.count == 0 && keywordModel.keywordRanks.count == 0 { refreshControl?.endRefreshing() return } keywordModel.keywordRanks = [Double]() keywordModel.keywordNames = [String]() keywordModel.averageRank = 0 keywordModel.keywordCount = 0 tableView.reloadData() // 避免强制解包崩溃 guard let keywords = selectedDomain?.keywords else { refreshControl?.endRefreshing() return } var rawKeys = keywords.compactMap { $0.name } // 递归串行请求 func fetchNextKeyword() { guard !rawKeys.isEmpty else { // 所有请求完成,切回主线程更新UI DispatchQueue.main.async { self.tableView.reloadData() self.refreshControl?.endRefreshing() } return } let currentKeyword = rawKeys.removeFirst() seo.fetchSEO(keyword: currentKeyword, requestURL: selectedDomain?.domainName, start: 1) { // 当前关键词请求完成,触发下一个请求 fetchNextKeyword() } } // 启动第一个请求 fetchNextKeyword() }
注意事项
- 所有请求的结束分支必须触发
completion回调,否则请求队列会卡住 URLSession的回调默认在后台队列,所有UI更新操作必须手动切回主队列- 如果需要中途取消请求,可以把当前的
dataTask存成全局属性,取消时调用cancel()方法即可
内容的提问来源于stack exchange,提问作者Aybatu Kerküklüoğlu
相关产品推荐
相关产品推荐

