【问题标题】:Subclass Type as closure parameter子类类型作为闭包参数
【发布时间】:2018-01-04 19:22:11
【问题描述】:

用例

我的 Firebase 中的大多数数据项(例如:RecipeItem、User)都有一个超类 (FirebaseObject) 和子类。我在超类中创建了一个自动更新子类中数据的函数,现在我正在尝试创建一个带有闭包的函数,该闭包在对象更新时被调用。


代码

class FirebaseObject {
    private var closures: [((FirebaseObject) -> Void)] = []

    public func didChange(completion: @escaping (((FirebaseObject) -> Void))) {
        // Save closures for future updates to object
        closures.append(completion)

        // Activate closure with the current object
        completion(self)
    }

    //...
}

这会使用初始对象调用闭包并将其保存以供以后更新。在我的 Firebase 观察者中,我现在可以在数据更新后通过调用激活所有闭包:

self.closures.forEach { $0(self) }

要添加这些侦听对象更改的闭包,我需要这样做:

let recipeObject = RecipeItem(data)

recipeObject.didChange { newFirebaseObject in
    // Need to set Type even though recipeObject was already RecipeItem 
    // this will never fail
    if let newRecipeObject = newFirebaseObject as? RecipeItem {
        // Do something with newRecipeObject
    }
}

问题

有没有办法让完成处理程序返回子类的类型,这样我就不必执行as? Subclass,即使它永远不会失败?我尝试使用泛型类型来执行此操作,但我无法弄清楚,我不确定这是否是正确的解决方案。

我想将大部分代码保留在 FirebaseObject 类中,因此在创建新子类时不需要添加大量代码。


编辑

基于this article我尝试在创建子类时添加类型:

class RecipeItem: FirebaseObject<RecipeItem> {
    //...
}

class FirebaseObject<ItemType> {
    private var handlers: [((ItemType) -> Void)] = []  

    public func didChange(completion: @escaping (((ItemType) -> Void))) {
        //...

这会编译,但是一旦 RecipeItem 初始化它就会崩溃。我也试过了

class RecipeItem: FirebaseObject<RecipeItem.Type> {
    //...
}

但是当我尝试访问 didChange 闭包中的 RecipeItem 数据时,这会产生一个有趣的编译器错误:

实例成员“title”不能用于类型“RecipeItem”

【问题讨论】:

    标签: swift firebase generics


    【解决方案1】:

    好的,所以我已经为此工作了一天,并且我找到了一种方法来使用 this answer 中的方法来处理 didChange 和 initObserver 函数,并从 this way of saving data in extensions 获得灵感。

    首先,所有需要使用子类类型的函数都移到了一个协议中。

    protocol FirebaseObjectType {}
    
    extension FirebaseObjectType where Self: FirebaseObject {
        private func initObserver(at ref: DatabaseReference) {
            //...
        }
    
        mutating func didChange(completion: @escaping (((Self) -> Void))) {
            if observer == nil {
                // init Firebase observer here so there will be no Firebase
                // observer running when you don't check for changes of the
                // object, and so the Firebase call uses the type of whatever
                // FirebaseObject this function is called on eg:
                //    RecipeItem.didChange returns RecipeItem
                // and NOT:
                //    RecipeItem.didChange returns FirebaseObject
                initObserver(at: ref)
            }
            if closureWrapper == nil {
                // init closureWrapper here instead of in init() so it uses
                // the class this function is called on instead of FirebaseObject
                closureWrapper = ClosureWrapper<Self>()
            }
    
            // Save closure for future updates to object
            closures.append(completion)
    
            // Activate closure with current object
            completion(self)
        }
    }
    

    为了保存闭包,我现在使用一个包装类,这样我就可以对其进行类型检查。在 FirebaseObject 中:

    class ClosureWrapper<T> {
        var array: [((T) -> Void)]
    
        init() {
            array = []
        }
    }
    
    fileprivate var closureWrapper: AnyObject?
    

    现在我可以在 FirebaseObjectType 协议中获得正确类型的闭包:

    private var closures: [((Self) -> Void)] {
        get {
            let closureWrapper = self.closureWrapper as? ClosureWrapper<Self>
    
            return closureWrapper?.array ?? []
        }
        set {
            if let closureWrapper = closureWrapper as? ClosureWrapper<Self> {
                closureWrapper.array = newValue
            }
        }
    }
    

    我现在可以在 FirebaseObject 子类上使用 didChange,而无需每次都检查其类型。

    var recipe = RecipeItem(data)
    
    recipe.didChange { newRecipe in
        // Do something with newRecipe 
    }
    

    【讨论】:

      猜你喜欢
      • 2019-08-28
      • 2021-02-13
      • 1970-01-01
      • 2019-11-10
      • 1970-01-01
      • 2015-09-04
      • 1970-01-01
      • 1970-01-01
      • 2012-11-25
      相关资源
      最近更新 更多