알고리즘을 풀다가 메모이제이션 관련된 해설을 보는데, 특이한 키워드가 눈에 들어왔다.
func fibo(_ num: Int, _ memo: inout [Int]) -> Int
이렇게 되어있었는데, 그냥 [Int]도 아니고 inout [Int]는 뭐지? 하고 좀 알아보았다.
"Swift 함수에 전달되는 모든 매개변수는 상수이므로 변경할 수 없습니다. 원하는 경우 하나 이상의 매개변수를 인아웃으로 전달하면 함수 내부에서 변경할 수 있으며, 변경된 값은 함수 외부의 원래 값에 반영됩니다."
라고 설명이 되어있었다.
inout
은 Swift에서 값을 복사하지 않고 참조를 전달하기 위해 사용하는 키워드다. 함수 내부에서 외부 변수에 접근해서 값을 직접 수정할 수 있다는 것인데, 간단한 코드라도 같이 봐야 이해가 쉬울 것 같아서 예시를 가져와봤다.
func addTen(_ x: inout Int) {
x = x + 10
}
var num = 10
addTen(&num)
print(num) //10 + 10 해서 20
이게 20이 찍히게 된다. inout 파라미터에 &으로 참조값을 전달하게 되면 함수 내부에서 이를 변경해주는 것이다.
만약에 inout키워드를 안쓴다면 ?
func addTen(_ x: Int) {
var number = x
number += 10
print("함수 내부값 \(number)") // 함수내부값 20
}
var num = 10
addTen(num)
print(num) //10
이렇게 num값을 그냥 복사해서 사용한 것이니까 함수외부의 num은 여전히 10이 찍히는 것을 확인할 수 있다. 위의 inout처럼 바로 값에 +10을 해줄수도 없기도 하겠지만.
inout
은 그냥 참조를 전달하는게 아니라 다음과 같은 과정을 거친다.
1. 변수 복사 함수 호출 시의 원래 변수 값 -> 임시 변수에 복사
2. 함수 실행 함수 내부에서 임시 변수 값 수정
3. 복사 반영 함수가 끝나면, 임시 변수 값 -> 원래 변수에 복사
이 과정을 통해 inout
은 참조 전달의 유연성을 제공하면서도 Swift의 값 타입 안정성을 유지합니다.
inout
의 차이점func addTen(_ x: inout Int) {
x = x + 10
}
var num = 10
addTen(&num)
print(num) //10 + 10 해서 20
inout
은num
을 복사해서x
로 넘기고, 함수가 끝난 후x
의 값을 다시num
에 복사하는 순서를 거치는 것이다.
이번에 경험했듯이, 피보나치를 메모이제이션으로 풀 때 활용 된 것 처럼 memo
를 함수 호출마다 복사하면 생길 수 있는 성능문제를 inout을 통해서 메모리 복사를 방지할 수 있다.
장점
**메모리 효율성
inout
을 사용하면 복사 대신 참조를 전달하기 때문에 메모리 사용량이 감소한다.**코드 간결성
주의점
복사 -> 수정 -> 다시 복사
함수 호출 시 변수만 전달 가능
inout
은 상수(let
)나 리터럴 값에는 사용할 수 없습니다.var num = 10
changeValue(&num) // OK
changeValue(&5) // 오류 발생: 리터럴 값 전달 불가
클로저에서는 inout 사용 불가
inout
매개변수는 사용할 수 없습니다. 이는 클로저가 참조를 캡처하는 방식과 충돌하기 때문입니다.inout의 수명 주기
inout 변수는 함수 실행 중에만 유효한데, 클로저는 함수가 끝나도 남아있는게 특징이다.
아까 언급했듯이, inout변수는 함수가 종료되면 복사본이 원래 변수에 복사되고, 더 이상 존재하지 않는데, 클로저는 실행 이후에도 참조를 시도하는데, 이때 변수가 이미 존재하지 않게 되기 때문에, 메모리 접근 오류가 발생할 수 있는 것이다.
func performAction(action: () -> Void) {
action()
}
func changeValue(_ value: inout Int) {
let closure = {
value += 1 // 오류 발생!
}
performAction(action: closure)
}
var myValue = 10
changeValue(&myValue) // 컴파일 오류
클로저가 value를 참조하려고 해도, value는 inout 매개변수라서 함수가 종료되면서 사라졌기 때문에 그 때, 클로저가 실행되는 시점에, inout매개변수인 value는 이미 사라져서 접근이 불가능하다.
느낀점
처음 inout
을 접했을 때는 그냥 '주소값을 전달해주는 구나'하는 느낌으로 참조를 전달한다고만 생각했는데, Swift
는 참조 전달 방식에서도 안전성을 보장하기 위해 복사-수정-복사의 방식을 거친다는 것에 좀 더 섬세하다 느꼈다. 문제를 풀때 마다 새로운 키워드가 나오니까 배울게 많다는게 와닿는다.