Swift - 24. 메모리 안전성 Memory Safety

지우개·2022년 4월 27일
0

Swift study

목록 보기
13/15
post-thumbnail

Swift는 코드에서 발생할 수 있는 안전하지 않은 동작을 방지한다.

Swift는 동일한 메모리 영역에 대한 다중 접근이 일어나지 않도록 막기 위해, 메모리의 위치를 수정하는 코드가 독점 접근 권한을 요구한다. Swift는 메모리를 자동으로 관리하므로 대부분 메모리 접근에 대해 생각할 필요가 없다. 그러나 잠재적 충돌에 대해 이해해야 발생할 수 있는 충돌을 피할 수 있다.


메모리에 충돌하는 접근 이해 Understanding Conflicting Access to Memory

코드의 다른 부분이 같은 시간에 메모리의 같은 위치에 접근할 때, 메모리의 충돌이 발생할 수 있다.

메모리 접근의 특징 Characteristics of Memory Access

고려해야 할 3가지 특성: 접근하는 동안 읽기 또는 쓰기 접근인지 여부와 메모리의 위치

아래의 조건을 만족하는 2개의 접근이 있을 때 충돌 발생:

  • 적어도 하나의 쓰기 접근이나 nonatomic 접근
  • 메모리의 같은 위치에 접근
  • 접근하는 시간이 겹침

쓰기 접근은 메모리의 위치를 변경하지만, 읽기 접근은 변경하지 않는다.

  • 즉각 접근
    • 접근이 시작되고 종료되기 전에 다른 코드를 실행할 수 없음. 즉시 접근이 이루어짐.
  • 장기 접근
    • 장기 접근은 시작되고 종료되기 전에 다른 코드가 실행될 수 있음 → 오버랩 overlap
    • 다른 장기 접근/즉각 접근과 오버랩될 수 있음
    • 겹치는 접근
      → 함수와 메소드에서 in-out 파라미터를 사용 / 구조체의 변경하는 메소드를 사용하는 코드에 주로 나타남

In-Out 파라미터에 충돌 접근 Conflicting Access to In-Out Parameters

  • 함수는 모든 in-out 파라미터에 장기 쓰기 접근을 가지고 있음
  • in-out 파라미터에 대한 쓰기 접근은 모든 non in-out 파라미터가 평가된 후에 시작되고 해당 함수 호출동안 지속됨.
  • in-out 파라미터가 여러 개인 경우 쓰기 접근은 파라미터가 나타나는 순서와 동일하게 시작됨

장기 접근의 결과 1

  • 범위 규칙과 접근 제어가 허용하더라도 기존에 대한 모든 접근은 충돌을 생성하므로 전단될 원래 변수에 접근할 수 없음. 해결: 복사본 지정
  • 아래 예제:
    var stepSize = 1 // 전역 변수
    
    func increment(_ number: inout Int) {
        number += stepSize
    }
    
    increment(&stepSize)
    // Error: conflicting accesses to stepSize
    
    // stepSize에 읽기 접근과 number에 쓰기 접근 오버랩됨
    // 같은 메모리를 참조하고 오버랩되어 충돌 발생
    
    // 충돌 해결 - stepSize의 복사본을 명확하게 지정
    // Make an explicit copy.
    var copyOfStepSize = stepSize
    increment(&copyOfStepSize)
    
    // Update the original.
    stepSize = copyOfStepSize
    // stepSize is now 2
    // 읽기 접근은 쓰기 접근이 시작되기 전에 끝나므로 충돌이 일어나지 않음

장기 접근의 결과 2

  • 같은 함수의 여러 개의 in-out 파라미터에 대해 인자로 단일 변수를 전달하면 충돌 발생
  • 아래 예제:
    func balance(_ x: inout Int, _ y: inout Int) {
        let sum = x + y
        x = sum / 2
        y = sum - x
    }
    var playerOneScore = 42
    var playerTwoScore = 30
    
    balance(&playerOneScore, &playerTwoScore)  // OK
    // 두 쓰기 접근은 동시에 오버랩되지만 메모리의 다른 위치를 접근하므로 충돌이 일어나지 않음
    
    balance(&playerOneScore, &playerOneScore)
    // Error: conflicting accesses to playerOneScore
    // 동시에 메모리의 같은 위치를 두 쓰기 접근이 수행하므로 충돌이 일어남

메소드에서 self에 충돌 접근 Conflicting Access to self in Methods

구조체의 변경 메소드는 메소드 호출 동안 self에 대한 쓰기 접근을 가짐.

  • 예제: 데미지를 입으면 줄어드는 체력량과 특수 능력을 사용하면 줄어드는 에너지량
    struct Player {
        var name: String
        var health: Int
        var energy: Int
    
        static let maxHealth = 10
        mutating func restoreHealth() {
            health = Player.maxHealth 
    				// self에 쓰기 접근은 메소드 반환될 때까지 유지
        }
    }
    
    extension Player {
    		// shareHealth(with:) 메소드는 in-out 파라미터로 다른 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. 중복 야기 X
    // oscar는 변경 함수에서 self의 값이기 때문에 oscar에 대한 쓰기 접근
    // maria는 in-out 파라미터로 전달되기 때문에 maria에 대한 쓰기 접근
    
    // 그러나 shareHealth(with:) 의 인자로 oscar를 전달하면 충돌 발생
    oscar.shareHealth(with: &oscar)
    // Error: conflicting accesses to oscar
    // 변경 메소드는 self에 대한 쓰기 접근이 필요, in-out 파라미터는 teammate에 쓰기 접근 필요
    // 메소드 내에서 self와 teammate는 메모리의 같은 위치를 참조, 쓰기 접근 오버랩되므로 충돌

프로퍼티에서 충돌 접근 Conflicting Access to Properties

  • 구조체, 튜플, 열거형과 같은 타입은 개별 구성값으로 구성됨. 값 타입이기 때문에 값의 일부분을 변경하면 전체 값이 변경됨 → 프로퍼티 중 하나에 읽기/쓰기 접근은 전체 값에 읽기/쓰기 접근을 요구함
var playerInformation = (health: 10, energy: 20)
balance(&playerInformation.health, &playerInformation.energy)
// Error: conflicting access to properties of playerInformation
// playerInformation에 중복 쓰기 접근이므로 충돌
// playerInformation.health, playerInformation.energy는 쓰기 접근이 필요한 in-out 파라미터

// 전역 변수에 저장된 구조체의 프로퍼티에 쓰기 접근 중복될 때 에러
var holly = Player(name: "Holly", health: 10, energy: 10)
balance(&holly.health, &holly.energy)  // Error

// holly 변수가 전역 변수가 아닌 지역 변수로 변경되면 구조체의 저장된 프로퍼티 중복 접근 안전
func someFunction() {
    var oscar = Player(name: "Oscar", health: 10, energy: 10)
    balance(&oscar.health, &oscar.energy)  // OK
}
// 위에서 oscar의 health, energy는 2개의 in-out 파라미터로 전달
// 2개의 저장된 프로퍼티는 상호작용하지 않으므로 안전
  • 구조체의 프로퍼티에 중복 접근에 대한 제한은 항상 필요한 것은 아님. 배타적 접근이 더 엄격한 요구사항임.

구조체의 프로퍼티에 중복 접근이 안전하다고 증명할 수 있는 경우:

  • 계산된 프로퍼티 또는 클래스 프로퍼티가 아닌 인스턴스의 저장된 프로퍼티만 접근
  • 구조체는 전역 변수가 아닌 지역 변수의 값임
  • 구조체는 클로저에 의해 캡쳐되지 않거나 nonescaping 클로저에 의해서만 캡쳐됨

0개의 댓글