首页 > 解决方案 > 如何使用通用协议作为委托?

问题描述

我有一个用于解析获取的数据的通用协议:

protocol DataParseDelegate: AnyObject {
    associatedtype FetchResult
    func didFetchData(data: FetchResult?, error: Error?)
}

extension DataParseDelegate {
    func didFetchData(data: FetchResult?, error: Error?) {
        // process data
    }
}

它位于解析为某种类型的视图控制器中:

class ParentViewController<T>: UIViewController {}
class ChildViewController: ParentViewController<DataModel>, DataParseDelegate {
    typealias FetchResult = DataModel
    
    override func viewDidLoad() {
        super.viewDidLoad()
        FirebaseService.delegate = self
        FirebaseService.shared.db.downloadData()
    }
    
    func didFetchData(data: DataModel?, error: Error?) {
        // process data
    }
}

我正在使用委托将获取的数据传递给视图控制器:

class FirebaseService {
    static let shared = FirebaseService()
    var db: Firestore! {
        let settings = FirestoreSettings()
        Firestore.firestore().settings = settings
        let db = Firestore.firestore()
        return db
    }
    weak var delegate: DataParseDelegate?
    
    func downloadData() {
        let first = db?.collection("myData")
            .order(by: "date")
            .limit(to: 8)
        
        first?.getDocuments(completion: { [weak self] (snapshot: QuerySnapshot?, error: Error?) in
            // handle errors
            self?.delegate?.didFetchData(data: nil, error: error)
            
            // parse the snapshot into DataModel
            self?.delegate?.didFetchData(data: dataModel, error: nil)
        }
    }
}

我收到以下错误:

协议 'DataParseDelegate' 只能用作通用约束,因为它具有指向委托的 Self 或关联类型要求:

weak var delegate: DataParseDelegate?

我尝试使用以下内容而不是associatedtype

protocol DataParseDelegate: AnyObject {
    func didFetchData<T>(data: [T]?, error: Error?)
}

extension DataParseDelegate {
    func didFetchData<T>(data: [T]?, error: Error?) {
        // process data
    }
}

但是,我收到一条错误消息:

无法推断通用参数“T”

关于代表:

first?.getDocuments(completion: { [weak self] (snapshot: QuerySnapshot?, error: Error?) in
    self?.delegate?.didFetchData(data: nil, error: error) <-----
}

标签: iosswiftgenericsswift-protocols

解决方案


对于具有关联类型的协议,编译器需要确切地知道符合对象的类型。为了实现这一点,我们可以直接使用符合要求的具体类型,也可以使用协议作为通用约束。除非我们提供一些额外的上下文,否则不会知道所引用的实际类型。所以你可以给 FirebaseService 一个具体的类型:

weak var delegate: ChildViewController?

这会起作用,但也许这不是你想要的。请阅读 John Sundell 的这篇优秀文章: 为什么不能直接引用某些协议,例如 Equatable 和 Hashable?


推荐阅读