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