티스토리 뷰

[Swift/iOS] 메모리 안전성 Memory Safety From 공식문서(1)

[Swift/iOS] 메모리 안전성 Memory Safety From 공식문서(2)

! 공식문서를 참고한 글입니다

 

메서드에서 Self로의 접근 충돌 Conflicting Access to Self in Methods

구조체의 mutating 메서드는 메서드 호출의 지속시간 동안 자기 자신(Self)을 향한 쓰기 접근을 갖는다

struct Player {
    var name: String
    var health: Int
    var energy: Int

    static let maxHealth = 10
    mutating func restoreHealth() {
        health = Player.maxHealth
    }
}

restoreHealth() 메서드는 자기 자신의 health 프로퍼티에 대한 쓰기 접근이 메서드 시작부터 끝까지 유지됨

접근 충돌이 일어날 수 있는 코드가 없음

 

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

oscar가 shareHealth메서드를 maria를 인자로 호출하는 것은 충돌을 발생시키지 않음

oscar는 mutating 메서드의 self의 값이기 때문에 메서드 호출 동안 oscar에 대한 쓰기 접근이 있음

그리고 inout 파라미터의 인자로 maria가 있기 때문에, maria도 같은 기간 동안 쓰기 접근이 있음

아래 그림에서 보이는 것처럼 서로 다른 메모리 위치에 대한 접근이기 때문에 같은 시간에 쓰기 접근을 하더라도 충돌을 발생시키지 않음

 

그러나, oscar가 전달인자로 oscar를 전달한다면 충돌이 있음

oscar.shareHealth(with: &oscar)
// Error: conflicting accesses to oscar

 

mutating 메서드는 메서드 지속시간 동안 self에 대해 쓰기 접근을 필요로 하는데, inout 파라미터 역시 같은 지속시간 동안 쓰기 접근을 필요로 함

메서드 안에서 self와 teammate가 같은 메모리 위치를 참조하게 됨

아래 그림처럼, 두 개의 쓰기 접근이 같은 메모리를 참고하고 중첩되면서 충돌이 발생함

 

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

구조체, 튜플, 그리고 열거형 같은 타입은 각각의 구성요소 값으로 이루어져 있음

값들은 값 타입이기 때문에, 값의 작은 일부 조각을 바꾸는 것은 전체 값을 바꾸는 것임

이것은 프로퍼티중 하나에 대해 쓰기나 읽기 접근이 전체 값의 쓰기나 읽기 접근을 필요로 한다는 것을 의미함

예를 들어, 튜플의 한 요소에 중첩한 쓰기 접근은 충돌을 일으킴

var playerInformation = (health: 10, energy: 20)
balance(&playerInformation.health, &playerInformation.energy)
// Error: conflicting access to properties of playerInformation

(balance 메서드의 첫 번째 전달인자와 두 번째 전달인자가 같은 메모리위치를 참고하고 동시에 접근해서 충돌 발생)

 

위의 예에서, 한 튜플의 element들을 balance에서 호출하는 것은 충돌을 일으킴

playerInformation에 대한 쓰기 접근이 중첩되어 일어나기 때문

playerInformation.health와 playerInformation.energy가 둘 다 inout 파라미터에 전달되었는데, 이것은 balance 메서드가 메서드의 지속시간 동안 그 전달인자들에 쓰기 접근을 필요로 한다는 것을 의미

이 경우에서 튜플의 element에 대한 하나의 쓰기 접근은 튜플 전체에 대한 하나의 쓰기 접근을 필요로 함

이것은 playerInformation이 대해 지속기간 동안 중첩된 두 개의 쓰기 접근이 있다는 것을 의미하고, 충돌을 일으킴

 

아래 코드는 한 구조체의 프로퍼티들에 대한 중첩된 쓰기 접근으로 같은 에러가 발생하는 것을 보여준다 

var holly = Player(name: "Holly", health: 10, energy: 10)
balance(&holly.health, &holly.energy)  // Error

 

전역 변수 대신 로컬 변수를 사용할 경우 안전하게 사용할 수 있음

func someFunction() {
    var oscar = Player(name: "Oscar", health: 10, energy: 10)
    balance(&oscar.health, &oscar.energy)  // OK
}

(이 부분은 온전히 이해가 되지 않음)

위의 예제에서는 두 저장된 속성이 상호작용하지 않기 때문에 컴파일러가 메모리 안전성이 지켜졌다는 것을 입증함

 

구조체의 프로퍼티 중복 접근에 대한 제한은 메모리 안전성을 위해 항상 필요한 것은 아님

메모리 안전성은 desired guarantee지만, 배타적 접근은 메모리 안전성보다 엄격한 필요사항임

이것은 어떤 코드가 메모리에 대한 배타적 접근을 위반하지만 메모리 안전성을 지키는 것을 의미함

Swift는 만약 컴파일러가 메모리에 대한 비배타적 접근이 여전히 안전하다는 것을 증명하면 이 메모리 안전 코드를 허용함

특히 다음 조건이 적용된다면, 구조체의 프로퍼티들에 대한 중첩 접근이 안전하다고 증명할 수 있음

  • 인스턴스의 저장된 속성만 접근하고 연산프로퍼티나 class 프로퍼티는 접근하지 않는 것
  • 구조체가 전역변수가 아닌 지역 변수인 것
  • 구조체가 클로저에 의해 캡처되지 않았거나 nonescaping 클로저에 의해 캡처됐을 것

컴파일러는 접근이 안전하다고 증명할 수 없다면, 그것을 허용하지 않음

 

 

 

 

댓글