具体来说,Swift内存管理如何使用委托模式与选项一起使用?
习惯于在Objective-C中编写委托模式,我的直觉是创建委托weak
。例如,在Objective-C中:
@property (weak) id<FooDelegate> delegate;
然而,在Swift中这样做并不是那么简单。
如果我们只有一个看似正常的协议:
protocol FooDelegate {
func doStuff()
}
我们不能将此类型的变量声明为弱:
weak var delegate: FooDelegate?
产生错误:
'weak'不能应用于非类型'FooDelegate'
因此,我们要么不使用关键字weak
,这允许我们使用structs
和enums
作为代理,或者我们将协议更改为以下内容:
protocol FooDelegate: class {
func doStuff()
}
允许我们使用weak
,但不允许我们使用structs
或enums
。
如果我不使我的协议成为类协议,因此不对我的变量使用weak
,我正在创建一个保留周期,对吗?
是否有任何可以想象的原因,为什么任何打算用作委托协议的协议都不应该是类协议,以便这种类型的变量可以是weak
?
我主要问一下,因为在the Apple official documentation on Swift protocols的委托部分中,它们提供了一个非类协议和一个非弱变量的示例,用作其类的委托:
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)
}
}
我们是否应该将此视为Apple认为我们应该使用结构作为代表的暗示?或者这只是一个不好的例子,并且实际上,委托协议应该被声明为仅类协议,以便委托对象可以保存对其委托的弱引用?
答案 0 :(得分:7)
我们是否应该将此视为Apple认为我们应该使用结构作为代表的暗示?或者这只是一个不好的例子,并且实际上,委托协议应该被声明为仅类协议,以便委托对象可以保存对其委托的弱引用?
这就是事情。在现实生活Cocoa编程中,代表可能是现有的类。它是一个类,因为它存在于某个其他目的,只有一个类可以满足 - 因为 Cocoa需要它。
例如,通常,以iOS为例,一个视图控制器需要充当另一个视图控制器的委托,以便在它们之间来回安排消息。视图控制器的所有权由视图控制器层次结构决定,而不是由其他任何东西决定。所以,在Swift中,就像在Objective-C中一样,你有更好制作delegate
属性weak
,因为如果一个视图控制器突然占用内存管理所有权会很糟糕另一个视图控制器!
因此,在Cocoa框架的现实世界中,存在不正确的所有权或保留周期的严重危险。这就是weak
解决的问题。但正如你正确地说,它只适用于课程。
然而,书中的例子是关于生活在一个抽象的人造Swift-only世界中的一些物体。在那个世界中,只要你没有循环的危险(保留周期),就没有理由不使用结构,并且没有理由担心内存管理。但是这个世界并不是你通常会编程的世界!而这个世界并不是你的Objective-C委托模式来自和属于的框架Cocoa世界。
答案 1 :(得分:2)
是的,这个例子有点奇怪。
因为该示例使用非类协议类型,所以它必须期望实现协议的可能结构,这意味着DiceGame
实例拥有其委托。事实上,这违反了关于代表模式的典型假设。
在这种情况下,它不会导致参考周期 因为DiceGameTracker
是一个虚构的对象,它不拥有DiceGame
本身 - 但是在真实中-world app即使可能,委托也可能是委托对象的所有者。 (例如,视图控制器可能拥有DiceGame
,并实现DiceGameDelegate
,以便它可以更新其UI以响应游戏事件。)
如果游戏,其委托或实现其中一个或两个协议的类型都是值类型,那种参考周期可能会变成混乱的混乱 - 因为值类型被复制,一些变量在你的设置最终将成为游戏(或游戏所有者)的不同副本。
实际上人们会期望使用引用类型(类)来实现它,即使协议声明留下了开启其他方式的可能性。即使在假设的仅限Swift的世界中,以这种方式执行它也是有意义的...通常每当您拥有长寿命,内部可变状态以及可能被其他多个角色访问的使用模式时,您想要一个类类型,即使你可以使用值类型和var
进行排序。
答案 2 :(得分:2)
如果您的协议中必须有structs
和emums
,那么如果您在关闭视图控制器之前创建了委托nil
,则会中断保留周期。我在仪器的Allocations中验证了这一点。
// view controller about to close
objectCreatedByMe.delegate = nil
这是可选的,所以这是有效的。