[Swift] [2일차] inout 파라미터

·2024년 12월 8일
0

SwiftAlgorithm

목록 보기
4/105
post-thumbnail

알고리즘을 풀다가 메모이제이션 관련된 해설을 보는데, 특이한 키워드가 눈에 들어왔다.

func fibo(_ num: Int, _ memo: inout [Int]) -> Int 

이렇게 되어있었는데, 그냥 [Int]도 아니고 inout [Int]는 뭐지? 하고 좀 알아보았다.

swift-공식 튜토리얼(inout)

"Swift 함수에 전달되는 모든 매개변수는 상수이므로 변경할 수 없습니다. 원하는 경우 하나 이상의 매개변수를 인아웃으로 전달하면 함수 내부에서 변경할 수 있으며, 변경된 값은 함수 외부의 원래 값에 반영됩니다."

라고 설명이 되어있었다.

inout의 원리와 동작 방식

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 동작 방식

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 

inoutnum을 복사해서 x로 넘기고, 함수가 끝난 후 x의 값을 다시 num에 복사하는 순서를 거치는 것이다.

이번에 경험했듯이, 피보나치를 메모이제이션으로 풀 때 활용 된 것 처럼 memo를 함수 호출마다 복사하면 생길 수 있는 성능문제를 inout을 통해서 메모리 복사를 방지할 수 있다.


장점

  1. **메모리 효율성

    • 배열, 딕셔너리 같은 데이터 구조를 함수로 전달할 때 inout을 사용하면 복사 대신 참조를 전달하기 때문에 메모리 사용량이 감소한다.
  2. **코드 간결성

    • 반환 값을 받을 필요 없이 함수 내부에서 바로 값을 수정하니 코드가 간결해진다.

주의점

  1. 복사 -> 수정 -> 다시 복사

    • 참조로 전달하지만, Swift는 복사-수정-복사 방식을 사용하므로 값 타입의 안전성을 보장합니다. 다만, 이로 인해 여러 스레드에서 동시에 수정할 경우 충돌이 발생할 수 있습니다.
  2. 함수 호출 시 변수만 전달 가능

    • inout은 상수(let)나 리터럴 값에는 사용할 수 없습니다.
    var num = 10
    changeValue(&num) // OK
    changeValue(&5)   // 오류 발생: 리터럴 값 전달 불가
  3. 클로저에서는 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는 참조 전달 방식에서도 안전성을 보장하기 위해 복사-수정-복사의 방식을 거친다는 것에 좀 더 섬세하다 느꼈다. 문제를 풀때 마다 새로운 키워드가 나오니까 배울게 많다는게 와닿는다.


참고 자료


profile
기억보단 기록을

0개의 댓글