Consider the following use case:
In a model for some game, you have a Player
class. Each Player
has an unowned let opponent: Player
which represents the opponent they are playing against. These are always created in pairs, and a Player
must always have an opponent
since it is non-optional. However, this is very difficult to model, since one player must be created before the other, and the first player will not have an opponent until after the second one is created!
Through some ugly hacking, I came up with this solution:
class Player {
private static let placeholder: Player = Player(opponent: .placeholder, name: "")
private init(opponent: Player, name: String) {
self.opponent = opponent
self.name = name
}
unowned var opponent: Player
let name: String
class func getPair(named names: (String, String)) -> (Player, Player) {
let p1 = Player(opponent: .placeholder, name: names.0)
let p2 = Player(opponent: p1, name: names.1)
p1.opponent = p2
return (p1, p2)
}
}
let pair = Player.getPair(named:("P1", "P2"))
print(pair.0.opponent.name)
print(pair.1.opponent.name)
Which works quite well. However, I am having trouble turning opponent
into a constant. One solution is to make opponent
a computed property without a set
, backed by a private var
, but I'd like to avoid this.
I attempted to do some hacking with Swift pointers, and came up with:
class func getPair(named names: (String, String)) -> (Player, Player) {
var p1 = Player(opponent: .placeholder, name: names.0 + "FAKE")
let p2 = Player(opponent: p1, name: names.1)
withUnsafeMutablePointer(to: &p1) {
var trueP1 = Player(opponent: p2, name: names.0)
$0.moveAssign(from: &trueP1, count: 1)
}
return (p1, p2)
}
But this gives a segfault. Furthermore, when debugging with lldb
, we can see that just after p1
is initialized, we have:
(lldb) p p1
(Player2.Player) $R3 = 0x0000000101004390 {
opponent = 0x0000000100702940 {
opponent = <uninitialized>
name = ""
}
name = "P1FAKE"
}
But at the end of the function, lldb shows this:
(lldb) p p1
(Player2.Player) $R5 = 0x00000001010062d0 {
opponent = 0x00000001010062a0 {
opponent = 0x0000000101004390 {
opponent = 0x0000000100702940 {
opponent = <uninitialized>
name = ""
}
name = "P1FAKE"
}
name = "P2"
}
name = "P1"
}
(lldb) p p2
(Player2.Player) $R4 = 0x00000001010062a0 {
opponent = 0x0000000101004390 {
opponent = 0x0000000100702940 {
opponent = <uninitialized>
name = ""
}
name = "P1FAKE"
}
name = "P2"
}
So p1
correctly points to p2
, but p2
still points to the old p1
. What's more, p1
has actually changed addresses!
My question is two-fold:
Is there a cleaner, more 'Swifty' way to create this structure of mutual non-optional references?
If not, what am I misunderstanding about
UnsafeMutablePointer
s and the like in Swift that makes the above code not work?