🔥 메서드에서 self에 대한 접근 충돌

432자
5분

구조체의 변경 메서드(mutating method)는 메서드 호출 동안 self에 대한 쓰기 접근을 가집니다. 예를 들어, 각 플레이어가 체력(health)과 에너지(energy)를 가지고 있는 게임을 생각해 봅시다. 체력은 데미지를 받을 때 감소하고, 에너지는 특수 능력을 사용할 때 감소하죠.

struct Player {
    var name: String
    var health: Int
    var energy: Int
 
    static let maxHealth = 10
    mutating func restoreHealth() {
        health = Player.maxHealth
    }
}
swift

위의 restoreHealth() 메서드에서 self에 대한 쓰기 접근은 메서드가 시작될 때 시작되고 메서드가 반환될 때까지 지속됩니다. 이 경우, restoreHealth() 내부에는 Player 인스턴스의 속성에 대해 겹치는 접근을 할 수 있는 다른 코드가 없어요. 아래의 shareHealth(with:) 메서드는 다른 Player 인스턴스를 in-out 매개변수로 받아, 겹치는 접근의 가능성을 만들어냅니다.

extension Player {
    mutating func shareHealth(with teammate: inout Player) {
        balance(&teammate.health, &health)
    }
}
 
var oscar = Player(name: "Oscar", health: 10, energy: 10)
var maria = Player(name: "Maria", health: 5, energy: 10)
oscar.shareHealth(with: &maria)  // OK
swift

위의 예제에서, Oscar의 플레이어가 Maria의 플레이어와 체력을 공유하기 위해 shareHealth(with:) 메서드를 호출하는 것은 충돌을 일으키지 않습니다. oscar는 변경 메서드에서 self의 값이므로 메서드 호출 동안 oscar에 대한 쓰기 접근이 있고, maria는 in-out 매개변수로 전달되었으므로 같은 기간 동안 maria에 대한 쓰기 접근이 있어요. 아래 그림에서 볼 수 있듯이, 이들은 메모리의 서로 다른 위치에 접근합니다. 두 쓰기 접근이 시간적으로 겹치더라도, 충돌하지는 않아요.

lecture image

하지만 oscarshareHealth(with:)의 인자로 전달하면, 충돌이 발생합니다:

oscar.shareHealth(with: &oscar)
// 오류: oscar에 대한 접근이 충돌합니다
swift

변경 메서드는 메서드 지속 시간 동안 self에 대한 쓰기 접근이 필요하고, in-out 매개변수는 같은 기간 동안 teammate에 대한 쓰기 접근이 필요해요. 메서드 내에서 selfteammate 모두 메모리의 같은 위치를 참조하죠. 아래 그림에서 볼 수 있듯이 말이에요. 두 쓰기 접근이 같은 메모리를 참조하고 겹치면서, 충돌이 발생하는 거예요.

lecture image

이런 상황을 방지하려면, 구조체의 변경 메서드에서는 self와 겹치는 쓰기 접근을 하는 in-out 매개변수를 사용하지 않는 것이 좋아요. 만약 꼭 필요하다면, 아래와 같이 self의 속성을 명시적으로 사용하는 것이 좋습니다:

extension Player {
    mutating func shareHealth(with teammate: inout Player) {
        balance(&teammate.health, &self.health)
    }
}
swift

이렇게 하면 selfteammate가 메모리의 다른 위치를 참조한다는 것이 명확해지죠.

또 다른 방법은 in-out 매개변수 대신 일반 매개변수를 사용하고 메서드에서 새로운 값을 반환하는 것입니다:

extension Player {
    func shareHealth(with teammate: Player) -> Player {
        var balancedTeammate = teammate
        balance(&balancedTeammate.health, &health)
        return balancedTeammate
    }
}
 
var oscar = Player(name: "Oscar", health: 10, energy: 10)
var maria = Player(name: "Maria", health: 5, energy: 10)
maria = oscar.shareHealth(with: maria)
swift

이렇게 하면 self에 대한 쓰기 접근과 teammate에 대한 쓰기 접근이 시간적으로 겹치지 않아 충돌을 피할 수 있어요.

구조체의 변경 메서드에서 self에 대한 쓰기 접근은 강력하지만, 주의해서 사용해야 합니다. self와 겹치는 쓰기 접근을 하는 in-out 매개변수를 피하고, 가능하다면 일반 매개변수와 반환 값을 사용하는 것이 안전한 코딩을 하는 데 도움이 될 거예요.