Search

[Swift] Functions

The Swift Programming Language Documents를 참고해서 정리했습니다. 여러 가지 생각들이 버무려져 있습니다.
contents
*이전 포스팅은 [Swift] Control Flow - Checking API Availability 에서 보실 수 있습니다.

Functions

함수는 특정 Task를 수행하는 코드 덩어리입니다. 우리가 어떤 기능을 실행하고자 할 때, 해당 기능을 수행해주는 함수를 호출하게 됩니다.
함수를 작성할 때, 우리는 3가지를 유념해서 작성해주면 됩니다.
1.
What the func does
2.
What it expect to receive
3.
What it return when it done

What the func does

“What the func does”를 나타내는 부분은 바로 “함수의 이름” 입니다.
함수의 이름을 통해 함수가 수행하고자 하는 Task를 알 수 있어야 합니다.
만약, 위의 함수의 이름이 exampleFunction였다면, 우리는 해당 함수가 어떤 일을 하는지 쉽게 파악하지 못할 겁니다. 이름을 보고 어떤 함수인지 전혀 유추할 수 없기 때문이죠.
즉, 함수의 이름만을 보고 “저 함수가 하고자 하는 일이 이런거구나!”를 바로 알 수 있어야 합니다.

What it expect to receive

“What it expect to receive”를 나타내는 부분은 바로 “함수의 파라미터” 입니다.
Swift에서는 함수 파라미터 부분에 2개의 이름을 설정할 수 있습니다. 이를 “parameter name”, “argument label” 이라고 부릅니다.
“argument label”은 함수 호출 시에 사용하는 부분으로 위의 예시에서는 to입니다.
“parameter name”은 함수 내부에서 사용하는 부분으로 위의 예시에서는 firstParameter입니다.
argument label은 생략될 수 있지만 parameter name은 필수적으로 작성해야 합니다. label 생략 시, parameter name은 label이 하는 역할까지 맡기 때문에 이 경우에는 parameter name을 사용해서 함수를 호출할 수 있습니다.
parameter name이 argument label의 역할을 할 수 있다면 argument label이 존재하는 이유는 뭔가요?
argument label를 사용해주면, 함수를 문장처럼 읽을 수 있습니다.
위의 예시에서도 to라는 label을 사용해서 함수 호출 시에 해당 코드가 문장처럼 읽히게 만들었습니다.
addTen(to: 20)
Swift
복사
argument label은 함수 바디 내에선 사용하지 않기 때문에 to라는 이름의 변수가 생기지 않아 함수 내부에서 가독성을 해칠 일이 없습니다. 오직 함수의 이름을 이해하기 쉽고 명확하게 만드는 일만 합니다.
다른 언어처럼 함수 호출 시, parameter name을 작성하기 싫다면 어떻게 해야 하나요?
함수 호출 시, 앞에 parameter name이나 argument label를 작성하고 싶지 않다면 underscore(_) 표시를 argument label 자리에 위치 시키면 됩니다.
함수 바디 내에서 해당 값을 표현하는 이름은 꼭 필요하기 때문에 parameter name은 필수적으로 작성해야 합니다.

같은 이름, 다른 Parameter

함수가 가지고 있는 이름이 같더라도 다른 파라미터를 가지고 있다면, 다른 함수로 취급합니다.
func greeting(name: String) func greeting(person: String)
Swift
복사
위의 두 함수는 다른 파라미터를 가지고 있기 때문에 다른 함수로 취급합니다.

Default Value

Swift는 파라미터에 default 값을 줄 수 있습니다.
default 값이 있는 경우에는 함수 호출 시 해당 파라미터에 전달할 값을 적지 않아도 괜찮습니다.
func add(firstParameter: Int, secondParameter: Int = 0) -> Int { return firstParameter + secondParameter } add(firstParameter: 10, secondParameter: 10) // 20 add(firstParameter: 20) // 20
Swift
복사
default value가 있는 parameter는 뒤쪽으로 배치하는 것이 좋습니다.
해당 parameter를 뒤로 배치하게 되면 default 값으로 인해 생략되는 파라미터가 중간 중간 생기지 않아 함수를 호출할 때에 작성하기 편합니다.

Variadic Parameters

여러개의 숫자 값을 더하는 함수를 만들 때, 대부분 이렇게 함수를 작성할 겁니다.
func addAllNumber(_ numbers: [Int]) -> Int { var sum: Int = 0 for num in numbers { sum += num } return sum }
Swift
복사
배열을 사용하면 여러 개의 숫자를 한 번에 받아올 수 있기 때문입니다. 하지만, 배열을 만들지 않고도 여러 개의 숫자를 한 번에 받아올 수 있는 방법이 있습니다. 바로, Variadic Parameters 입니다.
해당 파라미터는 Int... 방식으로 파라미터를 작성해주면 사용할 수 있습니다. ... 를 보면 함수 호출 시에 다양한 수의 input value를 전달할 수 있다고 이해하시면 됩니다.
위의 코드를 Variadic Parameters로 바꾸면 코드가 이렇게 바뀝니다.
func addAllNumber(_ numbers: Int...) -> Int { var sum: Int = 0 for num in numbers { sum += num } return sum }
Swift
복사
크게 달라진 점이 없죠? 위의 파라미터 부분에 ...를 추가한 것 밖에 없습니다. Variadic Parameter가 함수 바디 내에서 배열처럼 사용할 수 있기 때문이죠. 따라서, 배열을 만들지 않고도 여러 개의 값을 받아서 사용할 수 있게 됩니다.
만약, Variadic Parameter를 사용하는 함수가 다른 Parameter도 사용한다면 Swift Compiler가 코드를 이해하기 애매한 상황이 연출될 수도 있습니다. 왜냐하면, Variadic Parameter는 여러 input value를 ,(콤마)로 구분하기 때문이죠. 즉, Variadic Parameter 다음에 나오는 Parameter가 unnamed Parameter라면 Swift Compiler가 이해하지 못할 수도 있습니다.
따라서, Swift에서는 애초에 Variadic Parameter 뒤에 unnamed Parameter가 오지 못하게 막았습니다. 뒤에 쓰려고 하면 컴파일 에러가 발생합니다.

inout Parameter

혹시, 함수 호출 시 넣은 파라미터 값을 함수 내부에서 변경해보려고 한 적 있나요? 아마 변경이 안될겁니다.
함수 호출 시에 들어간 파라미터는 상수입니다. 그렇기 때문에, 함수 바디에서 해당 값을 바꾸려고 하면 컴파일 에러가 발생하게 됩니다. 함수가 해당 파라미터 값을 변경하지 못하게 하면서 실수로 값이 변경되는 문제를 막습니다.
하지만, 저는 파라미터로 들어간 값을 함수 바디에서 변경하고 싶어요.
이런 경우에 사용할 수 있는 방법이 바로 inout 키워드 입니다.
함수 파라미터 앞에 inout 키워드를 붙이고, 함수 호출 시 파라미터 값 앞에 & 표시를 해주면 됩니다. & 표시를 통해서 해당 값이 변경될 수 있다는 걸 내포해줍니다.
func swapTwoInts(_ a: inout Int, _ b: inout Int) { let temp = a a = b b = temp } swapTwoInts(&10, &50)
Swift
복사
inout 파라미터로 들어가는 값은 당연히 변수만 가능하고, default value를 사용할 수 없습니다. 또한, Variadic Parameter는 inout 키워드를 사용할 수 없습니다.
Parameter가 함수 내부에서 어떻게 변경되는 건가요?
⓵ 먼저, 함수 호출 시에 argument value가 복사됩니다. ⓶ 함수 바디에서 value를 변경합니다. ⓷ 함수가 반환되면서 copy된 값이 original argument로 할당됩니다.
이 과정을 copy-in copy-out 이라고 합니다.
해당 과정을 최적화하기 위해서 argument가 메모리의 물리적 주소에 저장된 값이라면 함수 내부와 외부에서 동일한 메모리 위치를 사용하게 됩니다. 이를 통해, 복사를 하면서 발생하는 오버헤드를 제거하고 copy-in copy-out의 요구사항을 충족시킬 수 있게 됩니다.
argument로 전달한 변수 사용을 조심해야겠네요.
original value를 현재 Scope에서 사용할 수 있는 상황이라고 하더라도 inout argument로 전달했다면 접근하지 않아야 합니다. 이는 동시 접근이 됩니다.
또한, 같은 변수를 inout 키워드를 사용하는 파라미터에 중복해서 전달하면 안됩니다.
var num: Int = 10 swapTwoInts(&num, &num) // ❌
Swift
복사
closure와 nested 함수에서도 inout parameter를 사용할 수 있나요?
사용할 수 있지만, inout parameter를 capture하는 closure, nested 함수는 non-escaping 해야 합니다. 즉, closure, nested 함수가 속해 있는 함수가 사라지기 전에 먼저 사라져야 하는거죠.
또한, inout parameter를 변경하지 않고 capture만 하는 경우에는 capture list를 사용해서 해당 parameter를 명시적으로 capture해야 합니다. capture list를 통해서 inout parameter가 복사된 경우에는 외부변수가 아닌 closure만의 복사본으로 사용하게 됩니다. 따라서, 외부 inout parameter 값이 변경되어도 내부에 영향을 끼치지 않게 됩니다.
만약, capture한 값을 변경해야 한다면 어떻게 해야 하나요?
inout parameter를 capture한 후에 변경해야 한다면, local copy를 사용하면 됩니다.
func someMutatingOperation(_ x: inout Int) { x = 1000 } func multithreadedFunction(queue: DispatchQueue, x: inout Int) { var localX = x defer { x = localX } queue.async { someMutatingOperation(&localX) } queue.sync { } } var num = 10 let queue = DispatchQueue(label: "duna") multithreadedFunction(queue: queue, x: &num) print(num) // 1000
Swift
복사
localX라는 local copy를 사용해서 localX 값을 inout parameter로 넣어 변경시키고 defer 블록을 통해서 함수 종료 시에 localX에 저장되어 있던 값을 x에 넣어줬습니다. 문제없이 num 값이 1000으로 변경된 걸 볼 수 있습니다.

What it return when it done

“What it return when it done”을 나타내는 부분은 바로 “함수의 리턴값” 입니다.
리턴값도 파라미터와 동일하게 있어도 되고 없어도 되는 부분입니다.
만약, 리턴값이 명시되어 있지 않다면 이는 해당 함수가 Void 타입을 반환한다는걸 의미합니다. 리턴할 값이 있다면 해당 값에 맞는 타입을 -> 뒤에 써주면 됩니다.
하지만, 리턴되는 값을 굳이 사용해야 하는 건 아닙니다. 사용하지 않는 경우도 있을 겁니다.
이럴 때는 함수 리턴값을 underscore(_)에 할당하면 됩니다. 이러면 return value를 무시할 수 있습니다.
let _ = addTen(to: 10)
Swift
복사
함수 리턴값은 무시할 수 있지만, 함수 내부에서 리턴해야 하는 값을 리턴하지 않는건 절대 안됩니다. 리턴 타입이 명시되어 있는 함수는 꼭 해당 타입을 어떠한 경우에도 리턴해줘야 합니다.
하지만, 모든 경우에 return 키워드를 쓰는건 아닙니다.
만약, 함수 내부에 single line만 존재하고 해당 line에 있는 값을 리턴하는 경우에는 return 키워드를 작성하지 않아도 됩니다. 이렇게 말이죠.
func greeting(name: String) -> String { "안녕하세요. \(name)" }
Swift
복사
이런 경우가 있을 수 있습니다. 리턴하는 값이 여러개인 경우입니다.
이때는 Tuple를 사용해서 Tuple에 해당 타입들을 담아주면 됩니다.
func minMax(nums: [Int]) -> (min: Int, max: Int) { return (nums.min(), nums.max()) }
Swift
복사
Tuple이기 때문에 label를 붙여서 해당 값이 무엇인지 명시적으로 나타낼 수 있습니다.

Function Type

Int, Double, String 등의 타입이 존재하듯 Function Type도 있습니다.
Function Type은 Parameter Type과 Return Type으로 구성됩니다.
위의 minMax 함수를 예를 들면 해당 Function Type은 ([Int]) -> (Int, Int) 입니다.
Function Type도 다른 Type들처럼 사용할 수 있습니다.
함수 Parameter, Return Type으로 사용할 수 있고, 변수나 상수 Type으로 사용할 수 있습니다.
let calculationFunction: (Int, Int) -> Int = addMethod calculationFunction(2, 3) // 5
Swift
복사
Parameter로 사용하는 경우에는 함수 호출하는 부분에서 일부 기능을 넘겨줄 수 있습니다.
func calculate(_ method: (Int, Int) -> Int, _ a: Int, _ b: Int) -> Int { method(a, b) } calculate(calculationFunction, 10, 20) // 30
Swift
복사
위의 코드에서는 Parameter로 (Int, Int) -> Int 타입을 받습니다. 위에서 선언한 calculationFunction이 동일한 타입을 가지기 때문에 해당 함수의 Parameter로 전달할 수 있게 됩니다.
이 경우 calculate에서는 기능을 따로 구현하지 않았지만 호출하는 부분에서 전달한 method를 사용해서 ab를 더하게 됩니다.

️ 참고 자료