子类类型作为闭包参数

时间:2018-01-03 12:46:14

标签: swift firebase generics

USECASE

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

代码

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'类型

1 个答案:

答案 0 :(得分:0)

好的,所以我一直在研究这一天,我已经找到了一种方法,可以使用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 
}