Swift

inout

Phililip
728x90

안녕하세요.

 

이번에는 Swift의 inout 키워드에 대해 알아볼게요.


Swift는 기본적으로 함수 파라미터를 상수값으로 취급해요.

 

그래서 아래와 같이 함수 안에서 파라미터 값을 수정하면 컴파일 에러가 발생합니다.

func increase(_ count: Int) {
    count += 1    ❎
}

// error: left side of mutating operator isn't mutable: 'count' is a 'let' constant
//     count += 1
//     ~~~~~ ^

 

즉, 개발자가 실수로 함수 안에서 값을 수정하는 것을 막기 위한 제약이라고 볼 수 있어요.

 

 

그럼에도 나는 꼭 함수 안에서 값을 바꿔야겠어!! 라는 경우라면 in-out 파라미터를 사용해주면 됩니다.

 

사용방법은 간단해요.

 

 

파라미터 타입 앞에 inout 키워드를 넣어주고, 함수를 호출할 때는 &(ampersand)를 변수 앞에 붙여주시면 됩니다.

(C언어 포인터와 사용방법이 상당히 유사하죠?)

func swapTwoInts(_ a: inout Int, _ b: inout Int) {
    let temporaryA = a
    a = b
    b = temporaryA
}

var someInt = 3
var anotherInt = 107
swapTwoInts(&someInt, &anotherInt)	// someInt = 107, anotherInt = 3으로 설정됨.

 

 

 

in-out 파라미터는 함수 내부에서 값을 수정될 수 있다는 것을 명시하는 것이기 때문에 argument로 상수값을 전달할 수 없습니다.

(컴파일 에러가 발생해요.)

func swapTwoInts(_ a: inout Int, _ b: inout Int) {
    let temporaryA = a
    a = b
    b = temporaryA
}

let someInt = 3  ❎
var anotherInt = 107
swapTwoInts(&someInt, &anotherInt)  ❎  // Cannot pass immutable value as inout argument: 'someInt' is a 'let' constant

 

 

 

inout 키워드를 썼을 때 내부적으로 어떻게 동작하는지 간단하게 알아볼게요.

 

in-out 파라미터는 아래와 같이 전달됩니다.

  • 함수 호출 시, argument 값 복사
  • 함수 안에서 값을 수정하는 경우, 위에서 복사한 argument 값이 수정되는 것임.
  • 함수가 종료될 때, 복사한 값을 original argument에 할당

 

정리하자면 내부적으로 값을 복사해서 사용하다가 함수가 종료될 때 복사한 값을 다시 할당해주는 것이에요.

 

이런 동작을 call by value result(또는 copy-in copy-out) 방식이라고 합니다.

 

한 가지 알아두었으면 하는 내용은, in-out 파라미터에 computed property 또는 property를 전달하는 경우 함수 호출될 때 getter가 한번 호출되고 함수가 종료될 때 setter가 한번 호출되는 것입니다.

(함수 안에서 값을 여러 번 수정했다고 해서 setter가 여러 번 호출되는 것이 아니니 주의해주세요!!)

 

 

또한 최적화를 위해서 argument가 메모리의 물리적 주소에 저장된 값인 경우에는 함수 내부와 외부에서 동일한 메모리 주소를 사용합니다. 최적화된 동작은 call by reference 방식으로 돌아갑니다.

(복사하는 오버헤드를 줄인 것이죠.)

(복사를 하지 않고 동일한 메모리 주소를 사용하면, getter-setter가 여러 번 불리는 것 아닌가요...?? 이 부분은 잘 모르겠어요..ㅠ)

 

 

 

in-out 파라미터를 사용할 때의 주의할 점이 몇 가지 있는데요.

 

in-out 파라미터를 사용하는 함수 내부에서 original argument 값을 직접 사용하게 되면 런타임 때 에러가 발생합니다.

var stepSize = 1

func increment(_ number: inout Int) {
    number += stepSize
}

increment(&stepSize)  ❎
// error: Execution was interrupted, reason: signal SIGABRT.
The process has been left at the point where it was interrupted, use "thread return -x" to return to the state before expression evaluation.

출처: https://docs.swift.org/swift-book/LanguageGuide/MemorySafety.html#ID569

 

 

동일한 메모리 주소에 대해서 read, write가 동시에 수행될 수 있기 때문이에요.

(단일 thread 환경에서는 함수 내부 처음에 read 하고 끝날 때 write 하기 때문에 문제가 없을 것 같은데... multi-thread 환경에서는 문제가 발생할 수 있겠죠..?)

 

굳이.. 하고 싶다면 함수 외부에서 original argument의 값을 복사해서 in-out 파라미터로 복사한 값을 넘겨주고 함수 내부에서는 original argument를 사용하면 되기는 합니다.

var stepSize = 1
var copyOfStepSize = stepSize  ✅

func increment(_ number: inout Int) {
    number += stepSize
}

increment(&copyOfStepSize)  ✅

 

 

 

비슷한 얘기로, 여러 in-out 파라미터에 동일한 변수를 전달할 때에는 컴파일 에러가 발생합니다.

(함수가 끝날 때 동일한 메모리에 동시에 write가 되기 때문이에요.)

func balance(_ x: inout Int, _ y: inout Int) {
    let sum = x + y
    x = sum / 2
    y = sum - x
}
var playerOneScore = 42
balance(&playerOneScore, &playerOneScore)  ❎
// Inout arguments are not allowed to alias each other
// Overlapping accesses to 'playerOneScore', but modification requires exclusive access; consider copying to a local variable

 

 

 

 

## 참고

- https://docs.swift.org/swift-book/LanguageGuide/Functions.html#ID173

- https://docs.swift.org/swift-book/ReferenceManual/Declarations.html#ID545

- https://docs.swift.org/swift-book/LanguageGuide/MemorySafety.html#ID569

 

 


 

이번 글은 여기서 마무리.

 

 

 

반응형

'Swift' 카테고리의 다른 글

@inlinable, @usableFromInline  (0) 2022.08.15
[Swift 5.7] if let shorthand  (0) 2022.07.05
Modeling errors  (0) 2022.05.27
클로저에서 [weak self] 사용할 때 주의할 점3  (0) 2022.05.21
클로저에서 [weak self] 사용할 때 주의할 점2  (0) 2022.04.24