You need to enable JavaScript to run this app. iOS开发:如何按队列顺序依次获取SEO查询API的返回结果
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.14 14:45:08