Swift - Inout 파라미터
Swift의 inout 파라미터에 대해 알아봅시다.
In-Out Parameters
Swift에서 함수의 파라미터는 상수(Constant)이므로 함수 내부에서 파라미터의 값을 변경할 수 없다. 이는 우리가 파라미터를 실수로라도 변경할 수 없다는 뜻이기도 하다. 만약 함수에서 파라미터의 값을 변경하고, 변경된 값이 함수 호출이 종료된 후에도 지속되길 원한다면 inout 파라미터를 사용하면 된다.
in-out 파라미터는 함수 정의시 파라미터의 타입 전에 inout 키워드를 추가하면 된다. in-out 파라미터는 변수(variable)만을 취급하며 함수의 인자로 전달할 때 &를 사용하여 해당 값이 함수내부에서 변경될 것임을 나타내야 한다.
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
let temp = a
a = b
b = temp
}
var someInt = 3
var anotherInt = 107
swapTwoInts(&someInt, &anotherInt)
// someInt = 107, anotherInt = 3
inout의 원리
inout 매개변수는 다음의 과정을 거친다.
- 함수가 호출되면, 매개변수로 넘겨진 변수가 복사된다.
- 함수 몸체에서, 복사한 값을 수정한다.
- 함수가 반환될 때, 변화된 값을 원본 변수에 재할당한다.
이 동작을 copy-in copy-out 혹은 call by value result 라고 부르며 inout은 실제로 copy-in copy-out의 줄임말 이다.
in-out 최적화
SWIFT는 in-out 파라미터에 대한 최적화를 위해 값을 복사하는 것이 아니라 값이 저장된 메모리 주소값을 함수 내,외부에서 사용한다. 이 최적화를 call by reference 라고 부른다. 이를 통해 복사로 인한 오버헤드를 줄일 수 있다. inout을 사용할 때 개발자가 따로 최적화를 고려할 필요는 없다.
메모리 접근
inout을 사용할 때는 메모리 접근에 주의해야 한다. 아래와 같은 상황에서 stepSize와 number는 같은 메모리 주소를 참조하기 때문에 읽기, 쓰기가 동시에 이루어져 충돌이 발생할 수 있기 때문이다.
var stepSize = 1
func increment(_ number: inout Int) {
number += stepSize
}
increment(&stepSize)
// Error: conflicting accesses to stepSize
inout 파라미터를 캡쳐할 수 있는 클로저, 중첩 함수는 반드시 nonescaping이여야 한다. inout 파라미터를 캡쳐하길 원한다면, 반드시 캡쳐리스트를 사용해서 해당 파라미터를 불변값으로 명시해야한다.
func someFunction(a: inout Int) -> () -> Int {
return { [a] in return a + 1 }
}
만약 값을 캡쳐하고, 변경시키길 원한다면 local copy를 사용하여 copy-in copy-out을 직접 구현해야한다. 멀티 스레드 코드에서 모든 변경이 함수 반환 전에 끝나야함을 보장하는 경우를 예로 들 수 있다.
func multithreadedFunction(queue: DispatchQueue, x: inout Int) {
// Make a local copy and manually copy it back.
var localX = x
defer { x = localX }
// Operate on localX asynchronously, then wait before returning.
queue.async { someMutatingOperation(&localX) }
queue.sync {}
}
Reference
https://docs.swift.org/swift-book/ReferenceManual/Declarations.html#ID545