快速内存管理是如何工作的?

时间:2022-12-26 16:10:52

Specifically, how does Swift memory management work with optionals using the delegate pattern?

具体地说,如何使用委托模式对选项进行快速内存管理?

Being accustomed to writing the delegate pattern in Objective-C, my instinct is to make the delegate weak. For example, in Objective-C:

由于习惯了在Objective-C中编写委托模式,我的直觉是使委托变得弱。例如,在objective - c中:

@property (weak) id<FooDelegate> delegate;

However, doing this in Swift isn't so straight-forward.

然而,在Swift中这样做并不是那么简单。

If we have just a normal looking protocol:

如果我们有一个正常的协议:

protocol FooDelegate {
    func doStuff()
}

We cannot declare variables of this type as weak:

我们不能将这种类型的变量声明为弱:

weak var delegate: FooDelegate?

Produces the error:

产生的错误:

'weak' cannot be applied to non-class type 'FooDelegate'

“弱”不能应用于非类类型的“FooDelegate”

So we either don't use the keyword weak, which allows us to use structs and enums as delegates, or we change our protocol to the following:

因此,我们要么不使用关键字soft,它允许我们使用structs和enums作为委托,要么将我们的协议更改为以下内容:

protocol FooDelegate: class {
    func doStuff()
}

Which allows us to use weak, but does not allow us to use structs or enums.

它允许我们使用弱,但不允许我们使用结构或枚举。

If I don't make my protocol a class protocol, and therefore do not use weak for my variable, I'm creating a retain cycle, correct?

如果我不让我的协议成为一个类协议,因此不为我的变量使用弱,我正在创建一个保留循环,对吗?

Is there any imaginable reason why any protocol intended to be used as a delegate protocol shouldn't be a class protocol so that variables of this type can be weak?

有什么可以想象得到的原因可以解释为什么任何打算作为委托协议的协议不应该是类协议,这样这种类型的变量就可以是弱的?

I primarily ask, because in the delegation section of the Apple official documentation on Swift protocols, they provide an example of a non-class protocol and a non-weak variable used as the delegate to their class:

我首先要问的是,因为在关于Swift协议的苹果官方文档的授权部分,他们提供了一个非类协议和一个作为类委托的非弱变量的例子:

protocol DiceGameDelegate {
    func gameDidStart(game: DiceGame)
    func game(game: DiceGame, didStartNewTurnWithDiceRoll diceRoll: Int)
    func gameDidEnd(game: DiceGame)
}
class SnakesAndLadders: DiceGame {
    let finalSquare = 25
    let dice = Dice(sides: 6, generator: LinearCongruentialGenerator())
    var square = 0
    var board: [Int]
    init() {
        board = [Int](count: finalSquare + 1, repeatedValue: 0)
        board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
        board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08
    }
    var delegate: DiceGameDelegate?
    func play() {
        square = 0
        delegate?.gameDidStart(self)
        gameLoop: while square != finalSquare {
            let diceRoll = dice.roll()
            delegate?.game(self, didStartNewTurnWithDiceRoll: diceRoll)
            switch square + diceRoll {
            case finalSquare:
                break gameLoop
            case let newSquare where newSquare > finalSquare:
                continue gameLoop
            default:
                square += diceRoll
                square += board[square]
            }
        }
        delegate?.gameDidEnd(self)
    }
}

Should we take this as a hint that Apple thinks we should be using structs as delegates? Or is this simply a bad example, and realistically, delegate protocols should be declared as class-only protocols so that the delegated object can hold a weak reference to its delegate?

我们是否应该把这看作是苹果认为我们应该使用结构作为代表的暗示呢?或者这只是一个糟糕的例子,实际上,委托协议应该被声明为仅面向类的协议,以便被委托的对象可以对其委托持有弱引用?

3 个解决方案

#1


7  

Should we take this as a hint that Apple thinks we should be using structs as delegates? Or is this simply a bad example, and realistically, delegate protocols should be declared as class-only protocols so that the delegated object can hold a weak reference to its delegate?

我们是否应该把这看作是苹果认为我们应该使用结构作为代表的暗示呢?或者这只是一个糟糕的例子,实际上,委托协议应该被声明为仅面向类的协议,以便被委托的对象可以对其委托持有弱引用?

Here's the thing. In real life Cocoa programming, the delegate is likely to be an existing class. It is a class because it exists for some other purpose that only a class can satisfy - because Cocoa demands it.

事情是这样的。在实际的Cocoa编程中,委托很可能是一个现有的类。它是一个类,因为它存在的目的是其他一些只有类才能满足的目的——因为Cocoa需要它。

For example, very often, to take iOS as an example, one view controller needs to act as another view controller's delegate for purposes of arranging a message back and forth between them. Ownership of the view controllers is dictated by the view controller hierarchy and by nothing else. So, in Swift, just as in Objective-C, you had better make that delegate property weak, because it would be terrible if one view controller suddenly took memory management ownership of another view controller!

例如,通常,以iOS为例,一个视图控制器需要作为另一个视图控制器的委托,以便在它们之间来回安排消息。视图控制器的所有权是由视图控制器层次结构决定的,不受其他任何约束。所以,在Swift中,就像在Objective-C中一样,你最好让那个委托属性变弱,因为如果一个视图控制器突然获得另一个视图控制器的内存管理所有权,那就太糟糕了!

So, in the real world of the Cocoa framework, there is serious danger of incorrect ownership or of a retain cycle. And that is the problem that weak solves. But it only works, as you rightly say, with classes.

因此,在可可框架的现实世界中,存在着不正确的所有权或保留周期的严重危险。这就是弱解的问题。但是,正如你说的,它只适用于类。

The example in the book, however, is about some objects living off in an abstract made-up artificial Swift-only world. In that world, as long as you aren't in danger of circularity (retain cycle), there's no reason not to use structs and there's no reason to worry about memory management. But that world is not the world you will usually be programming in! And that world is not the framework Cocoa world that your Objective-C delegate pattern comes from and belongs to.

然而,书中的例子是关于一些物体生活在一个抽象的人工合成的只有迅捷的世界里。在那个世界里,只要你没有循环(保留循环)的危险,就没有理由不使用结构体,也没有理由不担心内存管理。但那个世界不是你通常要编程的世界!这个世界不是你的Objective-C委托模式来自的可可框架世界,它属于可可框架。

#2


2  

Yes, this example is a bit of an oddity.

是的,这个例子有点奇怪。

Because the example uses a non-class protocol type, it has to expect a possible struct implementing the protocol, which means that the DiceGame instance owns its delegate. And indeed, that violates typical assumptions about the delegate pattern.

因为这个示例使用非类协议类型,它必须期望实现该协议的结构体,这意味着DiceGame实例拥有它的委托。实际上,这违背了委托模式的典型假设。

It doesn't lead to a reference cycle in this case because the DiceGameTracker is a fictional object that doesn't own the DiceGame itself — but in a real-world app it's possible, even likely, that a delegate might also be the owner of the delegating object. (For example, a view controller might own the DiceGame, and implement DiceGameDelegate so it can update its UI in response to game events.)

在这种情况下,它不会导致引用循环,因为DiceGameTracker是一个虚构的对象,它本身并不拥有DiceGame——但是在真实的应用程序中,一个委托也可能是委托对象的所有者。(例如,视图控制器可能拥有DiceGame,并实现DiceGameDelegate,以便它能够更新UI以响应游戏事件。)

That kind of reference cycle would probably turn into a confusing mess if either the game, its delegate, or the type implementing one or both of those protocols were a value type — because value types are copied, some of the variables in your setup would end up being distinct copies of the game (or game's owner).

这种参考周期可能会变成一个混乱混乱如果游戏,其委托,或实现一个或两个类型的协议是一个值类型——因为价值类型是复制,一些变量的设置会被不同的副本游戏或游戏的主人。

Realistically one would expect to use reference types (classes) to implement this anyway, even if the protocol declaration leaves open the possibility of doing it otherwise. Even in a hypothetical Swift-only world, it probably makes sense to do it that way... generally whenever you have something with long life, internal mutable state, and a usage pattern that has it being accessed by potentially multiple other actors, you want a class type, even if you can sort of fake otherwise with value types and var.

实际上,我们希望使用引用类型(类)来实现这一点,即使协议声明不允许这样做。即使是在一个假设的只有雨点的世界里,这样做也是有道理的……通常,当您拥有一个具有长生命期、内部可变状态和使用模式的对象时,您需要一个类类型,即使您可以使用值类型和var对其进行排序。

#3


2  

If you must have structs and emums in your protocol, then if you make your delegate nil just before you close the view controller, then this breaks the retain cycle. I verified this in Allocations in Instruments.

如果您的协议中必须有结构体和emums,那么如果您在关闭视图控制器之前使您的委托nil,那么这就打破了retain cycle。我在文书的分配中证实了这一点。

// view controller about to close
objectCreatedByMe.delegate = nil

It's an optional, so this is valid.

这是可选的,所以这是有效的。

#1


7  

Should we take this as a hint that Apple thinks we should be using structs as delegates? Or is this simply a bad example, and realistically, delegate protocols should be declared as class-only protocols so that the delegated object can hold a weak reference to its delegate?

我们是否应该把这看作是苹果认为我们应该使用结构作为代表的暗示呢?或者这只是一个糟糕的例子,实际上,委托协议应该被声明为仅面向类的协议,以便被委托的对象可以对其委托持有弱引用?

Here's the thing. In real life Cocoa programming, the delegate is likely to be an existing class. It is a class because it exists for some other purpose that only a class can satisfy - because Cocoa demands it.

事情是这样的。在实际的Cocoa编程中,委托很可能是一个现有的类。它是一个类,因为它存在的目的是其他一些只有类才能满足的目的——因为Cocoa需要它。

For example, very often, to take iOS as an example, one view controller needs to act as another view controller's delegate for purposes of arranging a message back and forth between them. Ownership of the view controllers is dictated by the view controller hierarchy and by nothing else. So, in Swift, just as in Objective-C, you had better make that delegate property weak, because it would be terrible if one view controller suddenly took memory management ownership of another view controller!

例如,通常,以iOS为例,一个视图控制器需要作为另一个视图控制器的委托,以便在它们之间来回安排消息。视图控制器的所有权是由视图控制器层次结构决定的,不受其他任何约束。所以,在Swift中,就像在Objective-C中一样,你最好让那个委托属性变弱,因为如果一个视图控制器突然获得另一个视图控制器的内存管理所有权,那就太糟糕了!

So, in the real world of the Cocoa framework, there is serious danger of incorrect ownership or of a retain cycle. And that is the problem that weak solves. But it only works, as you rightly say, with classes.

因此,在可可框架的现实世界中,存在着不正确的所有权或保留周期的严重危险。这就是弱解的问题。但是,正如你说的,它只适用于类。

The example in the book, however, is about some objects living off in an abstract made-up artificial Swift-only world. In that world, as long as you aren't in danger of circularity (retain cycle), there's no reason not to use structs and there's no reason to worry about memory management. But that world is not the world you will usually be programming in! And that world is not the framework Cocoa world that your Objective-C delegate pattern comes from and belongs to.

然而,书中的例子是关于一些物体生活在一个抽象的人工合成的只有迅捷的世界里。在那个世界里,只要你没有循环(保留循环)的危险,就没有理由不使用结构体,也没有理由不担心内存管理。但那个世界不是你通常要编程的世界!这个世界不是你的Objective-C委托模式来自的可可框架世界,它属于可可框架。

#2


2  

Yes, this example is a bit of an oddity.

是的,这个例子有点奇怪。

Because the example uses a non-class protocol type, it has to expect a possible struct implementing the protocol, which means that the DiceGame instance owns its delegate. And indeed, that violates typical assumptions about the delegate pattern.

因为这个示例使用非类协议类型,它必须期望实现该协议的结构体,这意味着DiceGame实例拥有它的委托。实际上,这违背了委托模式的典型假设。

It doesn't lead to a reference cycle in this case because the DiceGameTracker is a fictional object that doesn't own the DiceGame itself — but in a real-world app it's possible, even likely, that a delegate might also be the owner of the delegating object. (For example, a view controller might own the DiceGame, and implement DiceGameDelegate so it can update its UI in response to game events.)

在这种情况下,它不会导致引用循环,因为DiceGameTracker是一个虚构的对象,它本身并不拥有DiceGame——但是在真实的应用程序中,一个委托也可能是委托对象的所有者。(例如,视图控制器可能拥有DiceGame,并实现DiceGameDelegate,以便它能够更新UI以响应游戏事件。)

That kind of reference cycle would probably turn into a confusing mess if either the game, its delegate, or the type implementing one or both of those protocols were a value type — because value types are copied, some of the variables in your setup would end up being distinct copies of the game (or game's owner).

这种参考周期可能会变成一个混乱混乱如果游戏,其委托,或实现一个或两个类型的协议是一个值类型——因为价值类型是复制,一些变量的设置会被不同的副本游戏或游戏的主人。

Realistically one would expect to use reference types (classes) to implement this anyway, even if the protocol declaration leaves open the possibility of doing it otherwise. Even in a hypothetical Swift-only world, it probably makes sense to do it that way... generally whenever you have something with long life, internal mutable state, and a usage pattern that has it being accessed by potentially multiple other actors, you want a class type, even if you can sort of fake otherwise with value types and var.

实际上,我们希望使用引用类型(类)来实现这一点,即使协议声明不允许这样做。即使是在一个假设的只有雨点的世界里,这样做也是有道理的……通常,当您拥有一个具有长生命期、内部可变状态和使用模式的对象时,您需要一个类类型,即使您可以使用值类型和var对其进行排序。

#3


2  

If you must have structs and emums in your protocol, then if you make your delegate nil just before you close the view controller, then this breaks the retain cycle. I verified this in Allocations in Instruments.

如果您的协议中必须有结构体和emums,那么如果您在关闭视图控制器之前使您的委托nil,那么这就打破了retain cycle。我在文书的分配中证实了这一点。

// view controller about to close
objectCreatedByMe.delegate = nil

It's an optional, so this is valid.

这是可选的,所以这是有效的。