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

iOS开发:Worker类Delegate未调用问题求助

Delegate未触发:Stack Overflow API数据无法传递到ViewController

问题场景

已成功通过Worker类调用Stack Overflow API获取数据,但尝试用Delegate将数据传递至ViewController时,getListModels方法始终不执行。点击搜索按钮会触发Worker的fetch逻辑,但Delegate回调无响应。

相关代码

ViewController类

import UIKit

protocol WorkerDelegate: AnyObject {
    func getListModels(noteModels: [CellModel])
}

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, WorkerDelegate{
    private var listModels: [CellModel] = []
    private var searchText = UITextField()
    private var searchButton = UIButton()
    private var table = UITableView()
    private let worker = Worker()

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .systemBackground
        let worker = Worker()
        worker.workerDelegate = self
        setupSearchText()
        setupSearchButton()
        table.register(CustomCell.self, forCellReuseIdentifier: "Cell")
        table.delegate = self
        table.dataSource = self


        setupUI()
    }

    func getListModels(noteModels: [CellModel]) {
        listModels += noteModels
        table.reloadData()
    }

    private func setupSearchText() {
        searchText.translatesAutoresizingMaskIntoConstraints = false
        searchText.font = UIFont.systemFont(ofSize: 14, weight: .bold)
        searchText.textColor = .black
        searchText.backgroundColor = .white
        searchText.borderStyle = UITextField.BorderStyle.roundedRect
        searchText.layer.borderWidth = 1
        searchText.layer.borderColor = UIColor.black.cgColor
        view.addSubview(searchText)
        searchText.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
        searchText.leadingAnchor.constraint(
            equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 20
        ).isActive = true
        searchText.trailingAnchor.constraint(
            equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -70
        ).isActive = true
    }

    private func setupSearchButton() {
        searchButton.translatesAutoresizingMaskIntoConstraints = false
        searchButton.backgroundColor = .white
        searchButton.layer.borderWidth = 1
        searchButton.setTitle("OK", for: .normal)
        searchButton.setTitleColor(.black, for: .normal)
        searchButton.layer.borderColor = UIColor.black.cgColor
        view.addSubview(searchButton)
        searchButton.addTarget(self, action: #selector(didSearchButtonnTap(_:)), for: .touchUpInside)
        searchButton.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
        searchButton.leadingAnchor.constraint(
            equalTo: searchText.safeAreaLayoutGuide.trailingAnchor, constant: 10
        ).isActive = true
        searchButton.trailingAnchor.constraint(
            equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -20
        ).isActive = true
    }
    
    private func setupUI() {
        table.translatesAutoresizingMaskIntoConstraints = false
        table.separatorStyle = .none
        view.addSubview(table)

        table.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
        table.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
        table.topAnchor.constraint(equalTo: searchText.bottomAnchor,constant: 20).isActive = true
        table.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        listModels.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as? CustomCell else {
            fatalError("не CustomCell")
        }
        cell.selectionStyle = .none
        cell.model = listModels[indexPath.row]
        cell.layer.cornerRadius = 14
        cell.layer.shadowRadius = 14

        return cell
    }

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {}

    @objc private func didSearchButtonnTap(_ sender: Any) {
        worker.fetch(question: searchText.text ?? "")
        table.reloadData()
    }
}

Worker类

import Foundation

class Worker {
    let session: URLSession
    var workerDelegate: WorkerDelegate?

    func fetch(question: String) {
        var listModels: [CellModel] = [CellModel(title: "123",name: "312",date: Date.now, answer_count: Int32(0))]
        if let url = createURLComponents(question: question) {
            url.asyncDownload { data, _, error in
                guard let data = data else {
                    print("URLSession dataTask error:", error ?? "nil")
                    return
                }

                do {
                    let jsonObject = try JSONDecoder().decode(SearchModels.self, from: data)
                    _ = String(data: data, encoding: .utf8)
                    for object in jsonObject.items {
                        let timeInterval = TimeInterval(object.creationDate)
                        let newDate = Date(timeIntervalSince1970: timeInterval)
                        var model = CellModel(
                            title: object.title,
                            name: object.owner.displayName,
                            date: newDate,
                            answer_count: Int32(object.answerCount)
                        )
                        listModels.append(model)
                    }
                    DispatchQueue.main.async {
                        self.workerDelegate?.getListModels(noteModels: listModels)
                    }
                } catch {
                    print(error.localizedDescription)
                }
            }
        }
    }

    init(session: URLSession = URLSession(configuration: .default)) {
        self.session = session
    }

    private func createURLComponents(question: String) -> URL? {
        var urlComponents = URLComponents()
        urlComponents.scheme = "https"
        urlComponents.host = "api.stackexchange.com"
        urlComponents.path = "/2.3/search"
        urlComponents.queryItems = [
            URLQueryItem(name: "order", value: "desc"),
            URLQueryItem(name: "sort", value: "activity"),
            URLQueryItem(name: "intitle", value: question),
            URLQueryItem(name: "site", value: "stackoverflow")
        ]
        return urlComponents.url!
    }

    private func createURLRequest(question: String) -> URLRequest {
        var request = URLRequest(url: createURLComponents(question: question)!)
        request.httpMethod = "GET"
        return request
    }
}

extension URL {
    func asyncDownload(completion: @escaping (_ data: Data?, _ response: URLResponse?, _ error: Error?) -> Void) {
        URLSession.shared
            .dataTask(with: self, completionHandler: completion)
            .resume()
    }
}

问题原因

ViewController中存在两个独立的Worker实例:

  • 类属性private let worker = Worker():后续调用fetch方法的实例,但从未设置它的workerDelegate
  • viewDidLoad中的局部实例let worker = Worker():给这个实例设置了workerDelegate = self,但实例在viewDidLoad执行完毕后就被销毁,未被实际使用

最终导致调用fetch的Worker实例的workerDelegate为nil,无法触发回调。

修复方案

删除viewDidLoad中的局部Worker实例,直接给类属性的worker设置delegate:

override func viewDidLoad() {
    super.viewDidLoad()
    view.backgroundColor = .systemBackground
    worker.workerDelegate = self
    setupSearchText()
    setupSearchButton()
    table.register(CustomCell.self, forCellReuseIdentifier: "Cell")
    table.delegate = self
    table.dataSource = self

    setupUI()
}

同时移除搜索按钮点击事件中多余的table.reloadData()——此时数据还未更新,调用无意义,Delegate回调中已经包含了数据更新后的刷新逻辑:

@objc private func didSearchButtonnTap(_ sender: Any) {
    worker.fetch(question: searchText.text ?? "")
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 21:11:26