import UIKit
var robGlobal = Player(name: "Rob", health: 10, energy: 10)
var matt = Player(name: "Matt", health: 5, energy: 10)
struct Player {
var name: String
var health: Int
var energy: Int
static let maxHealth = 10
mutating func restoreHealth() {
health = Player.maxHealth
}
func balance(_ x: inout Int, _ y: inout Int) {
let sum = x + y
x = sum / 2
y = sum - x
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
//MARK1: -Conflicting Access to self in Methods
robGlobal.shareHealth(with: &matt)
robGlobal.shareHealth(with: &robGlobal) // NOT OK in compile time Error: //Overlapping accesses to 'robGlobal', but modification requires exclusive access; consider copying to a local variable
var robNewLocal = Player(name: "Rob", health: 10, energy: 10)
robNewLocal.shareHealth(with: &robNewLocal) // NOT OK in compile time
//MARK2: -Conflicting Access to properties in Methods
robGlobal.someFunction() // OK
robGlobal.anotherFunction() // NOT OK in Run Time Error: Thread 1: Simultaneous accesses to 0x108cc4050, but modification requires exclusive access
}
}
extension Player {
mutating func shareHealth(with teammate: inout Player) {
balance(&teammate.health, &health)
}
func someFunction() {
var robLocal = Player(name: "Rob", health: 10, energy: 10)
balance(&robLocal.health, &robLocal.energy) // OK
}
func anotherFunction() {
balance(&robGlobal.health, &robGlobal.energy) // NOT OK in Run Time
}
}
I am trying to understand memory safety in Swift, I am confused between two scenarios.
As I marked MARK1: -Conflicting Access to self in Methods section makes sense. Two write access makes overlap each other. Already compiler gives me error before run :
Overlapping accesses to 'robGlobal', but modification requires exclusive access; consider copying to a local variable
QuestionOne: Okay but what about MARK2 section? Swift compiler makes some optimization to solve this write access here for struct?
Apple explain indicate for solution as below but it is not clear for me.
The compiler can prove that memory safety is preserved because the two stored properties don’t interact in any way.
QuestionTwo: By making global in here run time problem solves. But I wonder how ? Also why it doesn't work for MARK1 section and why ?
PS: Apple Document is here
With robGlobal.someFunction()
, you're calling shareHealth
on a separate local instance of robLocal
. Obviously there are no memory safety issues in that case.
But with robGlobal.anotherFunction()
, you're then calling an instance method of the robGlobal
, but then passing a yet another reference to the same robGlobal
to shareHealth
, hence the memory safety violation.
But that is good. This is functionally equivalent to one of the other attempts that the memory safety checks protected you against:
robGlobal.shareHealth(with: &robGlobal)
The whole idea is to share health with another player. Setting aside the memory safety checks, what does it even mean to share one player’s health with itself? E.g. your current health is 11. You then “share” it with yourself. So, is your health now 5? Or 6? And does it make sense that your health was 11 and now that after you've shared it with yourself that it is now a smaller value? The whole idea of sharing with yourself doesn't make sense. The “memory safety” checks are protecting you from this sort of misuse.
Personally, I would
make balance
a static
method because all it is doing it “balancing” two integer values, not doing anything with the current instance;
this should also probably be private as there is no need to expose this function; and
since you seem to want to “share health” to balance heath between two players as well as balance the health and energy for a given player, I would write those two methods.
Thus, we end up with something like:
struct Player {
var name: String
var health: Int
var energy: Int
static let maxHealth = 10
}
// MARK: - Interface
extension Player {
mutating func restoreHealth() {
health = Player.maxHealth
}
mutating func shareHealth(with teammate: inout Player) {
Self.balance(&teammate.health, &health)
}
mutating func balanceHealthAndEnergy() {
Self.balance(&health, &energy)
}
}
// MARK: - Private utility methods
private extension Player {
static func balance(_ x: inout Int, _ y: inout Int) {
let sum = x + y
x = sum / 2
y = sum - x
}
}
Then you can do something like:
func someFunction() {
var rob = Player(name: "Rob", health: 20, energy: 10)
rob.balanceHealthAndEnergy()
}
func anotherFunction() {
var rob = Player(name: "Rob", health: 10, energy: 10)
var matt = Player(name: "Matt", health: 5, energy: 10)
rob.shareHealth(with: &matt)
}
This new interface (the shareHealth
and balanceHeathAndEnergy
) streamline the interface. You can call them on local vars or properties (or globals, if you really want to; but having globals is generally a bad idea). But you would never shareHealth
with yourself, and now that balance
is private, it minimizes the sort of misuse outlined in your question.
FWIW, I would not make these test methods instance methods of Player
as they are not doing anything with the current instance. You could make them static methods. Or you could just make them methods in your model (or tests or whatever). But they do not make sense as Player
instance methods.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With