스위프트에서 함수 내부에서 발생하는 사건은 함수 외부에 영향을 미칠 수 없다. 함수 내부와 외부에 동일한 인자값이 존재하지만 함수 내부에서 변경된 인자값은 함수 외부의 인자값에 아무런 영향도 끼칠 수 없다. 이는 단순히 같은 값을 가지고 있을 뿐, 둘은 단절된 서로 다른 객체이기 때문이다.
var cnt = 30
func autoIncrement(value: Int) -> Int {
var value = value
value += 1
return value
}
print(autoIncrement(value: cnt)) // 함수 내부의 value 값 : 31
print(cnt) // 외부에서 정의된 cnt 변수값 : 30
cnt 변수를 autoIncrement(value:) 함수의 인자값으로 입력하면 31이된다. 하지만 인자값으로 사용된 변수 자체의 값은 아무런 변화가 없기 때문에, 여전히 값이 30인걸 볼 수 있다.
이는 외부에서 입력한 인자값이 직접 함수 내부로 전달되는 것이 아니라 그 값이 복사된 다음 전달되기 때문이다. 즉, 인자값으로 전달된 cnt 매개변수는 value와 서로 다른 변수라는 것이다.
하지만 함수에서 내부에 수정된 인자값이 외부까지 영향을 미칠 수 있는 방법이 존재한다. 반환값을 이용하지 않고 말이다. 이를 위해 사용되는 키워드가 inout이다. 이 키워드를 이용하면 스위프트에서는 함수 내부에서 수정된 인자값을 함수 외부까지 전달할 수 있다.
func foo(paramCount: inout Int) -> Int {
paramCount += 1
return paramCount
}
이렇게 inout 키워드가 붙은 매개변수는 인자값이 전달될 때 새로운 내부 상수를 만들어 복사하는 대신 인자값 자체를 함수 내부로 전달한다. 사실 inout 키워드의 정확한 의미는 값 자체를 전달하는 것이 아니라 저장된 메모리 주소를 전달한다는 의미이다. 인자값에 할당된 데이터가 저장되어 있는 메모리 주소를 함수에 전달하는 것이다.
C언어에서의 포인터와 유사한 개념이라고 할 수 있다. 이 때문에 inout 키워드가 사용된 함수는 호출 시 주의가 필요하다. 인자값을 전달할 때 값이 아닌 주소를 전달해야 하기 때문이다. 따라서 inout 매개변수에 들어갈 인자값에는 주소 추출 연산자 &(C와 동일)를 붙여주어야 정상적으로 전달할 수 있다. '&' 연산자는 변수나 상수 앞에 붙어서 값이 저장된 메모리 주소를 읽어오는 역할을 한다.
var count = 30
print(foo(paramCount: &count)) // 함수 내부의 paramCount값 : 31
print(count) // 외부에서 정의된 count 값 :31
이처럼 주소를 전달하는 방식을 프로그래밍 용어로 '참조(Reference)에 의한 전달'이라고 하며, 기존처럼 값을 복사하여 전달하는 것을 '값에의한 전달'이라고 한다. 이 두가지에 대해 좀 더 자세히 알아보자.
값에 의한 전달과 참조에 의한 전달
값에 의한 전달은 인자값을 전달하면 내부적으로 값의 복사가 이루어져서 복사된 값을 이용해 구문을 실행하는 것을 의미한다. 이 방식은 내부적으로 복사를 통해 새로운 변수나 상수를 이용해 함수의 기능을 실행하므로 인자값의 수정이 발생하더라도 원본 데이터에는 영향을 미치지 않는다.
우리가 많이 사용하는 String, Int, Double, Float, Bool 등 기본 자료형들 대부분이 이처럼 값에의한 전달 방식으로 인자값을 전달한다. 원본값은 그대로 둔 채 복사된 새로운 값이 전달되는 것이다. 따라서 우리는 인자값을 내부에서 수정하더라도 외부값의 변경을 고려할 필요가 없다. 값이 전달되는 순간 내부 인자값과 외부 인자값은 서로 남이 되기 때문이다.
반면 참조에 의한 전달은 내부적으로 복사가 이루어지는 대신 값이 저장된 주소가 전달된다. 인자값을 저장하고 있는 객체 자체가 전달된다고 보면된다.이 방식은 외부의 인자값을 직접 참조하므로 함수 내부에서 인자값이 수정되면 그 결과가 인자값 원본에도 고스란히 반영된다.
이처럼 '참조에 의한 전달'은 함수에서 inout 키워드를 사용했을 때 적용되지만, 예외적으로 클래스(Class)로 구현된 인스턴스는 inout 키워드를 사용하지 않아도 항상 참조에 의해 전달된다.
또한 주의할 점이 있는데 바로 inout키워드가 붙은 매개변수에 인자값을 입력할 때는 인자값 객체의 종류에 주의해야한다. 함수 내부에서 원본 객체에 직접 값을 수정할 수 있어야 하므로 상수는 전달 대상이 될 수 없다. 같은 이유로 리터럴 역시 전달 대상이 될 수 없다. 오직 변수만 인자값으로 사용할 수 있다.
var i = 10
let j = 20
func increment(param: inout Int) -> Int {
param += 1
return param
}
increment(param: &i) // ( O ) // 11
increment(param: &j) // ( X ) let(상수)이라 값의 변경 자체가 불가
increment(param: &5) // ( X ) 리터럴은 따로 주소를 가지고 있지 않기에 불가
출처 : 꼼꼼한 재은씨의 Swift 문법편
'Swift > 문법' 카테고리의 다른 글
스위프트(Swift) - 함수(Function) Ⅵ. 일급 함수의 특성 (0) | 2022.05.19 |
---|---|
스위프트(Swift) - 함수(Function) Ⅴ. 변수의 생존 범위와 생명 주기 (0) | 2022.05.18 |
스위프트(Swift) - 함수(Function) Ⅲ. 가변인자, 기본값, 수정 (0) | 2022.05.16 |
스위프트(Swift) - 함수(Function) Ⅱ. 내부 매개변수, 외부 매개변수 (0) | 2022.05.13 |
스위프트(Swift) - 함수(Function) Ⅰ. 함수의 기본 개념 (0) | 2022.05.12 |