Swift 捕获计算字符串时的引用循环 属性 从实例方法内部声明

Swift Reference Cycle When Capturing A Computed String Property Declared From Inside An Instance Method

我正在使用 Swift 和 SpriteKit 实现一个 in-game 商店。有一个名为 Store 的 class,它有一个方法 setupItems(),我们在其中声明和实例化 class StoreItem 的实例,并添加每个商店项目实例作为 Store 的 child。每个 StoreItem 都有一个名为 updateInventory 的可选闭包 属性,它也在 setupItems() 内部设置。这是一个可选的关闭,因为一些项目没有有限的库存。

Store还有一个unowned实例属性storeDelegate,它负责决定如何扣除资金以及如何在购买后应用storeItemsstoreDelegateunowned,因为它的生命周期等于或大于 Store

现在,事情变得有趣了 - 闭包 updateInventory 引用了一个名为 itemText 的计算字符串变量,它根据 storeDelegate 的 属性 进行计算.如果 itemText 被声明并实例化为 setupItems() 内的变量,我们有一个引用循环并且存储不会被释放。相反,如果 itemText 被声明并实例化为 Store 的实例 属性(就在它引用的 属性 unowned storeDelegate 下方),则没有引用循环一切都在应该的时候解除分配。

这似乎暗示在 class 的实例方法中从计算变量引用 storeDelegate 不遵守 unowned 限定符。代码示例如下:

当前场景

protocol StoreDelegate: AnyObject {
    func subtractFunds(byValue value: Int)
    func addInventoryItem(item: InventoryItem) throws
    var player: Player! { get }
}

class Store: SKSpriteNode, StoreItemDelegate {
    unowned var storeDelegate: StoreDelegate
    
    init(storeDelegate: StoreDelegate) {
        self.storeDelegate = storeDelegate
        setupItems()
        ...
    }
    func setupItems() {
        var itemText: String { 
            return "item info goes here \(storeDelegate.player.health)"
        }
        
        let storeItem = StoreItem(name: "coolItem")
        storeItem.updateInventory = {
            [unowned storeItem, unowned self] in
            // some logic to check if the update is valid
            guard self.storeDelegate.player.canUpdate() else {
                return
            }
            storeItem.label.text = itemText
        }

    }

以上导致引用循环,有趣的是如果我们移动

var itemText: String { 
    return "item info goes here \(storeDelegate.player.health)"
}

updateItems之外,在unowned var storeDelegate: StoreDelegate的正下方做成Store的实例变量,就没有循环引用了。

我不知道为什么会这样,也没有在文档中看到它。如有任何建议,我们将不胜感激,如果您需要任何其他详细信息,请告诉我。

storeItem.updateInventory 现在保持对 itemText.

的强引用

我认为问题在于 itemText 隐含地持有对 self 的强引用以访问 storeDelegate,而不是保留对 storeDelegate 的引用。另一种选择是,即使 self 将委托保留为 unowned,一旦您将 ot 传递给 itemText 进行保留,它就会被管理(即再次强引用)。

无论哪种方式,您都可以保证不保持强引用将 itemText 更改为函数并直接传递委托:

func setupItems() {
  func itemText(with delegate: StoreDelegate) -> String
    return "item info goes here \(storeDelegate.player.health)"
  }
  let storeItem...
  storeItem.updateInventory = {
    // ...
    storeItem.label.text = itemText(with self.storeDelegate)
   }
 }