Swift - 如何在释放调用者时从数组中正确删除块?

Swift - How to properly remove blocks from an array when a caller is deallocated?

我有一个 'updateBlocks' (闭包) 的数组,我在单例中使用 class 来通知任何观察者 (UIViewControllers等) 数据更新时。

我想知道删除观察者的最佳方法是什么,以便在观察者被释放(或不再需要更新)时不执行观察者。

这是我当前的设置:

MySingletonClass

var updateBlock: (() -> ())? {
    didSet {
        self.updateBlocks.append(updateBlock!)
        self.updateBlock!() // Call immediately to give initial data
    }
}
var updateBlocks = [() -> ()]()

func executeUpdateBlocks() {
    for block in updateBlocks {
        block()
    }
}

我的观察者Class

MySingleton.shared.updateBlock = {
    ...handle updated data...
}

MySingleton.shared.updateBlock = nil // How to properly remove???

你的单例设计有一些问题。

updateBlock 作为 didSet 方法将块附加到您的 updateBlocks 数组的变量是糟糕的设计。

我建议去掉 updateBlock 变量,而是定义一个 addUpdateBlock 方法和一个 removeAllUpdateBlocks 方法:

func addUpdateBlock(_ block () -> ()) {
   updateBlocks.append(block)
}

func removeAllUpdateBlocks() {
   updateBlocks.removeAll()
}
func executeUpdateBlocks() {
    for block in updateBlocks {
        block()
    }

}

如果您想删除单个块,那么您将需要一些方法来跟踪它们。正如 rmaddy 所说,每个块都需要某种 ID。您可以将块的容器重构为字典并使用顺序整数键。当你添加一个新块时,你的 addBlock 函数可以 return key:

var updateBlocks = [Int: () -> ()]()
var nextBlockID: Int = 0

func addUpdateBlock(_ block () -> ()) -> Int {
   updateBlocks[nextBlockID] = block
   let result = nextBlockID
   nextBlockID += 1

   //Return the block ID of the newly added block
   return result
}

func removeAllUpdateBlocks() {
   updateBlocks.removeAll()
}

func removeBlock(id: Int) -> Bool {
   if updateBlocks[id] == nil {
     return false 
   } else {
     updateBlocks[id] = nil
     return true
   }

func executeUpdateBlocks() {
    for (_, block) in updateBlocks {
        block()
    }

如果您将块保存在字典中,那么它们将不会以任何定义的顺序执行。

这是一个非常令人困惑的问题 API。从客户的角度来看,您正在设置单个块的值。但是实现实际上将该块添加到一个数组中,然后立即调用该块。为什么要强制展开可选块?

既然你想支持多个观察者并提供移除观察者的能力,你真的在​​你的单例中显示了 addBlockremoveBlock 方法。那么API和它的功能就很清楚了。

诀窍是如何提供一个 API 让观察者告诉单例删除特定块。我将在 NotificationCenter class 中如何完成 API 建模,其中 addBlock 方法 returns 一些生成的令牌。然后将该令牌传递给 removeBlock 方法。

实现可能是以令牌为键的字典,值是块。令牌可以是 UUID 或其他生成的唯一不透明值。这使得 addBlockremoveBlock 方法变得简单。然后 executeBlocks 方法将迭代字典的值并调用这些块。

这是一种可能的实现方式:

class UpdateBlocks {
    static let shared = UpdateBlocks()

    var blocks = [UUID: () -> ()]()

    private init() {
    }

    func addBlock(_ block: @escaping () -> ()) -> Any {
        let token = UUID()
        blocks[token] = block

        return token
    }

    func removeBlock(_ token: Any) {
        if let token = token as? UUID {
            blocks[token] = nil
        }
    }

    func executeBlocks() {
        for (_, value) in blocks {
            value()
        }
    }
}

let token = UpdateBlocks.shared.addBlock {
    print("hello")
}

UpdateBlocks.shared.executeBlocks() // Outputs "hello"

UpdateBlocks.shared.removeBlock(token)

UpdateBlocks.shared.executeBlocks() // No output