함수 (Functions)는 특정 작업을 수행하는 코드 모음입니다. Swift의 통합 함수 구문은 파라미터 이름이 없는 단순한 C 스타일 함수에서 각 파라미터에 대한 이름과 인수가 있는 복잡한 Objective-C 스타일까지 모든 것을 표현할 수 있을 만큼 유연합니다. 파라미터는 함수 호출을 단순화하기 위해 기본 값을 제공하며, 함수가 실행을 완료하면 전달된 변수를 수정할 수 있는 in-out 파라미터를 전달할 수 있습니다.
Swift의 함수에는 함수 파라미터 타입과 반환 타입으로 구성된 타입이 있습니다. Swift의 다른 타입과 마찬가지로 이 타입을 사용할 수 있으므로 함수를 파라미터로 다른 함수에 전달하고 함수에서 함수를 반환할 수 있습니다. 함수는 중첩된 함수 범위내에서 유용한 기능을 캡슐화하기 위해 다른 함수 내에 작성될 수 도 있습니다.
함수 정의와 호출 (Defining and Calling Functions)
함수를 정의할 때 파라미터를 여러개 추가할 수 있고, 반환 타입을 정의할 수 있습니다.
모든 함수는 함수가 수행하는 작업을 설명하는 함수 이름을 가지고 있습니다. 함수를 사용하려면 함수의 이름으로 호출(Call)할 때, 함수의 파라미터와 일치하는 인수를 입력 값을 전달해야 합니다. 함수의 인수는 항상 함수의 파라미터 순서와 동일하도록 정의해야 합니다.
아래 예시의 greet(person:) 함수는 사람의 이름을 입력으로 받아 그 사람의 인사말을 반환합니다. 이 함수는 String 타입인 person 값과 String 타입의 인사말에 대한 반환 타입을 정의합니다.
func greet(person: String) -> String {
let greeting = "Hello, " + person + "!"
return greeting
}
함수를 정의할 때는 func 키워드를 앞에 붙여 함수를 정의합니다. 또한, 반환 화살표 -> 뒤에 반환 타입을 붙여 함수의 반환 타입을 정의합니다.
print(greet(person: "Anna"))
// Prints "Hello, Anna!"
print(greet(person: "Brian"))
// Prints "Hello, Brian!"
함수 파라미터와 반환 값 (Function Parameters and Return Values)
Swift에서는 함수 파라미터와 반환 값을 유연하게 사용할 수 있습니다. 이름이 지정되지 않은 단일 파라미터가 있는 간단한 유틸리티 함수에서 파라미터 이름과 다른 파라미터 옵션이 있는 복잡한 함수에 이르기까지 모든 것을 정의할 수 있습니다.
파라미터 없는 함수 (Functions Without Parameters)
아래 코드처럼 파라미터가 없는 함수를 정의할 수 있습니다.
func sayHelloWorld() -> String {
return "hello, world"
}
print(sayHelloWorld())
// Prints "hello, world"
여러개 파라미터가 있는 함수 (Functions With Multiple Parameters)
아래 코드처럼 여러개의 파라미터가 있는 함수를 정의할 수 있습니다.
func greet(person: String, alreadyGreeted: Bool) -> String {
if alreadyGreeted {
return greetAgain(person: person)
} else {
return greet(person: person)
}
}
print(greet(person: "Tim", alreadyGreeted: true))
// Prints "Hello again, Tim!"
위 함수는 person, alreadyGreeted라는 두개의 파라미터를 가집니다.
반환 값 없는 함수 (Functions Without Returns Values)
아래 코드처럼 반환 타입이 없는 함수를 정의할 수 있습니다. 반환값이 필요없기 때문에 반환 화살표 (->)와 반환 타입을 정의할 필요가 없습니다.
func greet(person: String) {
print("Hello, \(person)!")
}
greet(person: "Dave")
// Prints "Hello, Dave!"
아래 코드에서 첫번째 함수 printAndCount(String:)은 문자열을 출력하고 문자 갯수를 Int로 반환합니다. 두번째 함수 printWithoutCount(string:)은 첫번째 함수를 호출하지만 반환값을 무시합니다. 두번째 함수가 호출될 때 첫번째 함수에 의해 메세지는 출력되지만 반환된 값은 사용하지 않습니다.
func printAndCount(string: String) -> Int {
print(string)
return string.count
}
func printWithoutCounting(string: String) {
let _ = printAndCount(string: string)
}
printAndCount(string: "hello, world")
// prints "hello, world" and returns a value of 12
printWithoutCounting(string: "hello, world")
// prints "hello, world" but does not return a value
여러개의 반환값이 있는 함수 (Functions with Multiple Return Values)
함수가 여러개의 값을 반환하기 위해 함수의 반환 타입으로 튜플 타입을 사용할 수 있습니다.
아래 예시는 Int 타입의 배열에서 가장 작은값과 가장 큰 값을 찾는 minMax(array:) 함수를 정의합니다.
func minMax(array: [Int]) -> (min: Int, max: Int) {
var currentMin = array[0]
var currentMax = array[0]
for value in array[1..<array.count] {
if value < currentMin {
currentMin = value
} else if value > currentMax {
currentMax = value
}
}
return (currentMin, currentMax)
}
minMax(array:) 함수는 2개의 Int 값을 가진 튜플을 반환합니다. 이 값들은 함수의 반환 값을 조회할 때 이름으로 접근할 수 있도록 min과 Max로 라벨되어 있습니다.
그래서 튜플의 멤버 이름은 함수의 반환 타입의 일부로 이미 지정되어 있으므로 함수에서 튜플이 반환되는 시점에 이름을 지정할 필요가 없습니다.
let bounds = minMax(array: [8, -6, 2, 109, 3, 71])
print("min is \(bounds.min) and max is \(bounds.max)")
// Prints "min is -6 and max is 109"
옵셔널 튜플 반환 타입 (Optional Tuple Return Types)
함수에서 반환되는 튜플 타입이 전체 튜플에 대해 "값이 없을" 가능성이 있는 경우 옵셔널 튜플 반환 타입을 사용하여 전체 튜플이 nil 일 수 있다는 사실을 반영할 수 있습니다. (Int, Int)? 또는 (String, Int, Bool)?와 같이 튜플 타입의 닫는 소괄호 다음에 물음표를 붙여 옵셔널 튜플 반환 타입을 정의합니다.
위에서 minMax(array:) 함수는 2개의 Int 값을 포함하는 튜플을 반환합니다. 그러나 이 함수는 배열이 전달될 때 아무런 안정성을 확인하지 않습니다. array 인수가 빈 배열을 포함하면 위에서 정의된 minMax(array:) 함수는 array[0]을 접근할 때 런타임 에러가 발생합니다.
빈 배열을 안전하게 처리하기 위해선 minMax(array:) 함수는 옵셔널 튜플 반환 타입을 가지고 배열이 비어있을 때 nil을 반환합니다.
func minMax(array: [Int]) -> (min: Int, max: Int)? {
if array.isEmpty { return nil }
var currentMin = array[0]
var currentMax = array[0]
for value in array[1..<array.count] {
if value < currentMin {
currentMin = value
} else if value > currentMax {
currentMax = value
}
}
return (currentMin, currentMax)
}
옵셔널 바인딩을 통해 minMax(array:) 함수가 실제 튜플 값을 반환하는지 nil을 반환하는지 확인할 수 있습니다.
if let bounds = minMax(array: [8, -6, 2, 109, 3, 71]) {
print("min is \(bounds.min) and max is \(bounds.max)")
}
// Prints "min is -6 and max is 109"
암시적 반환을 가진 함수 (Functions With an Implicit Return)
함수의 전체 본문이 한줄로 표현이 된다면 이 함수는 맹목적으로 해당 표현식을 반환합니다. 예를 들면 아래의 두 함수는 모두 같은 동작을 합니다.
func greeting(for person: String) -> String {
"Hello, " + person + "!"
}
print(greeting(for: "Dave"))
// Prints "Hello, Dave!"
func anotherGreeting(for person: String) -> String {
return "Hello, " + person + "!"
}
print(anotherGreeting(for: "Dave"))
// Prints "Hello, Dave!"
위의 greeting(for:) 함수는 anotherGreeting(for:) 함수와 동일한 동작을 하지만, return 키워드를 생략할 수 있습니다.
함수 인수 라벨과 파라미터 이름 (Function Argument Labels and Parameter Names)
각 함수 파라미터는 인수 라벨(argument label)과 파라미터 이름(parameter name)을 가지고 있습니다. 인수 라벨은 함수가 호출될 때 사용되고 각 인수는 함수 호출 시 인수 라벨 다음에 작성합니다. 파라미터 이름은 함수를 구현할 때 사용됩니다. 기본적으로 파라미터는 인수 라벨로 파라미터 이름을 사용합니다.
func someFunction(firstParameterName: Int, secondParameterName: Int) {
// In the function body, firstParameterName and secondParameterName
// refer to the argument values for the first and second parameters.
}
someFunction(firstParameterName: 1, secondParameterName: 2)
모든 파라미터는 유니크한 이름을 가져야 합니다. 여러 파라미터에 동일한 인수 라벨을 가질수도 있지만 유니크한 인수 라벨을 붙여야 더 읽기 편한 코드로 작성할 수 있습니다.
인수 라벨 지정 (Specifying Argument Labels)
공백으로 구분하여 파라미터 이름 앞에 인수 라벨을 작성합니다.
func someFunction(argumentLabel parameterName: Int) {
// In the function body, parameterName refers to the argument value
// for that parameter.
}
다음은 사람의 이름과 고향을 가져와 인사말을 반환하는 greet(person:) 함수의 변형입니다.
func greet(person: String, from hometown: String) -> String {
return "Hello \(person)! Glad you could visit from \(hometown)."
}
print(greet(person: "Bill", from: "Cupertino"))
// Prints "Hello Bill! Glad you could visit from Cupertino."
인수 라벨을 사용하면 문장과 같은 표현방식으로 함수를 호출할 수 있는 동시에 읽기 쉽고 의도가 명확한 함수 본문을 제공할 수 있습니다.
인수 라벨 생략 (Omitting Argument Labels)
파라미터에 인수 라벨을 원치 않으면 명시적인 인수 라벨 대신에 언더바(_)를 작성합니다.
func someFunction(_ firstParameterName: Int, secondParameterName: Int) {
// In the function body, firstParameterName and secondParameterName
// refer to the argument values for the first and second parameters.
}
someFunction(1, secondParameterName: 2)
파라미터가 인수 라벨을 가지고 있다면 함수를 호출할 때 인수는 반드시 라벨을 지정해야 합니다.
파라미터 기본값 (Default Parameter Values)
파라미터의 타입 뒤에 파라미터 값을 할당하여 함수의 파라미터에 기본값(default value)을 정의할 수 있습니다. 기본값이 정의되어 있다면 함수를 호출할 때 파라미터를 생략할 수 있습니다.
func someFunction(parameterWithoutDefault: Int, parameterWithDefault: Int = 12) {
// If you omit the second argument when calling this function, then
// the value of parameterWithDefault is 12 inside the function body.
}
someFunction(parameterWithoutDefault: 3, parameterWithDefault: 6) // parameterWithDefault is 6
someFunction(parameterWithoutDefault: 4) // parameterWithDefault is 12
기본값이 없는 파라미터는 함수의 파라미터 리스트 시작부분에 위치하고 기본값이 있는 파라미터 전에 위치합니다. 기본 값이 없는 파라미터는 일반적으로 함수의 의미적으로 더 중요합니다. 이를 먼저 작성하면 기본 파라미터가 생략되었는지 여부에 관계없이 동일한 함수가 호출되고 있음을 쉽게 인식할 수 있습니다.
가변 파라미터 (Variadic Parameter)
가변 파라미터(variadic parameter)는 0개 이상의 특정 타입의 값을 허용합니다. 함수가 호출될 때 여러개의 입력값이 전달될 수 있는 특정 파라미터는 가변 파라미터를 사용합니다. 가변 파라미터는 파라미터의 타입 이름 뒤에 세개의 간격 문자(...)를 추가하여 작성합니다.
가변 파라미터에 전달된 값은 함수 본문 내에서 적절한 타입의 배열로 사용할 수 있습니다. 예를 들어 numbers 라는 이름과 Double... 타입을 가진 가변 파라미터는 함수 본문 내에서 [Double] 타입의 numbers라 불리는 상수 배열로 사용할 수 있습니다.
아래 예시는 길이에 관계없이 숫자 리스트에 대한 산술 평균을 계산합니다.
func arithmeticMean(_ numbers: Double...) -> Double {
var total: Double = 0
for number in numbers {
total += number
}
return total / Double(numbers.count)
}
arithmeticMean(1, 2, 3, 4, 5)
// returns 3.0, which is the arithmetic mean of these five numbers
arithmeticMean(3, 8.25, 18.75)
// returns 10.0, which is the arithmetic mean of these three numbers
함수는 여러개의 가변 파라미터를 가질 수 있습니다. 가변 파라미터 뒤에 오는 첫번째 파라미터는 인수 라벨을 가지고 있어야 합니다. 인수 라벨은 가변 파라미터에 전달되는 인수와 가변 파라미터 뒤에 오는 파라미터에 전달되는 인수를 명확하게 합니다.
In-Out 파라미터 (In-Out Parameters)
함수 파라미터는 기본적으로 상수입니다. 해당 함수의 본문 내에서 함수 파라미터 값을 변경하려고 하면 컴파일 타입 에러 발생합니다. 이것은 실수로 파라미터의 값을 변경할 수 없다는 것을 의미합니다. 함수의 파라미터 값을 변경하고 함수 호출이 종료된 후에도 이러한 변경된 값을 유지하고 싶다면 in-out 파라미터 (in-out paremeter)로 대신 정의해야합니다.
in-out 파라미터를 사용하려면 파라미터의 타입 바로 전에 inout 키워드를 작성하면 됩니다. in-out 파라미터는 함수로 전달하는 값을 가지고 있고 함수로 부터 이 값을 수정하고 원래 값을 대체하기 위해 함수 밖으로 다시 되돌려 줍니다.
in-out 파라미터의 인수로 변수만 전달할 수 있습니다. 상수와 반복은 수정할 수 없기 때문에 인수로 상수 또는 반복 값은 전달할 수 없습니다. 함수에 수정가능함을 알리기 위해 in-out 파라미터에 인수로 전달할 때 변수의 이름 앞에 앰퍼샌드(&)를 붙여줍니다.
아래 예시의 함수는 a와 b라는 2개의 in-out 정수 파라미터를 가지는 swapTwoInts(_:_:) 입니다.
func swapTwoInts(_ a: inout Int, _ b: inout Int) {
let temporaryA = a
a = b
b = temporaryA
}
swapTwoInts(_:_:) 함수는 간단하게 b의 값을 a로는 a의 값을 b로 바꿉니다. 이 함수는 a의 값을 temporaryA라 하는 임시 상수에 저장하고 b의 값을 a에 할당하고 temporaryA의 값을 b에 할당합니다.
Int 타입의 바꿀 2개의 변수를 이용하여 swapTwoInts(_:_:) 함수를 호출할 수 있습니다. swapTwoInts(_:_:) 함수에 전달할 때 someInt 와 anotherInt 의 이름 앞에 앰퍼샌드(&)를 붙여야 합니다.
var someInt = 3
var anotherInt = 107
swapTwoInts(&someInt, &anotherInt)
print("someInt is now \(someInt), and anotherInt is now \(anotherInt)")
// Prints "someInt is now 107, and anotherInt is now 3"
위 예시는 someInt와 anotherInt의 기본값이 함수의 바깥에서 정의되었지만 swapTwoInts(_:_:) 함수로 인해 원래값이 수정되는 것을 보여줍니다.
함수 타입 (Function Types)
모든 함수는 파라미터 타입과 반환 타입으로 구성된 특정 함수 타입 (fuinction type)이 있습니다.
아래 예시는 2개의 Int 파라미터를 받고, 적ㄹ절한 수학 연산을 수행하여 Int 값을 반환합니다.
func addTwoInts(_ a: Int, _ b: Int) -> Int {
return a + b
}
func multiplyTwoInts(_ a: Int, _ b: Int) -> Int {
return a * b
}
아래 예시는 파라미터 또는 반환 값이 없는 함수입니다.
func printHelloWorld() {
print("hello, world")
}
즉, 이 함수의 타입은 () -> Void와 같습니다.
함수 타입 사용 (Using Function Types)
Swift에서 다른 타입처럼 함수 타입을 사용합니다. 예를 들어 함수 타입에 대해 상수 또는 변수로 정의할 수 있고 변수에 적절한 함수 타입을 할당할 수 있습니다.
var mathFunction: (Int, Int) -> Int = addTwoInts
위 함수는 "2개의 Int값을 파라미터로 가지고, Int 타입의 반환값을 가지는 mathFunction 이라는 변수"를 정의한 것입니다.
즉, 아래와 같이 mathFunction 이라는 이름으로 할당된 함수를 호출할 수 있습니다.
print("Result: \(mathFunction(2, 3))")
// Prints "Result: 5"
비함수 타입과 동일한 방식으로 같은 타입으로 일치하는 다른 함수를 같은 변수에 할당할 수 있습니다.
mathFunction = multiplyTwoInts
print("Result: \(mathFunction(2, 3))")
// Prints "Result: 6"
다른 타입과 마찬가지로 상수 또는 변수에 함수를 할당할 때 함수 타입을 추론하기 위해 Swift에 맡길 수 있습니다.
let anotherMathFunction = addTwoInts
// anotherMathFunction is inferred to be of type (Int, Int) -> Int
파라미터에 함수 타입을 사용 (Function Types as Parameter Types)
(Int, Int) -> Int 와 같은 함수 타입을 다른 함수의 파라미터로 사용할 수 있습니다. 이렇게 하면 함수 호출자가 함수가 호출될 때 제공할 함수 구현의 일부를 남겨둘 수 있습니다.
func printMathResult(_ mathFunction: (Int, Int) -> Int, _ a: Int, _ b: Int) {
print("Result: \(mathFunction(a, b))")
}
printMathResult(addTwoInts, 3, 5)
// Prints "Result: 8"
위의 printMathResult(_:_:_:)의 역할은 적절한 타입의 수학 함수를 호출하고 값을 출력합니다. 이 함수는 함수의 실질 동작 구현이 어떻게 되는지는 없습니다.
반환 타입의 함수 타입 (Function Types as Return Types)
다른 함수의 반환 타입으로 함수 타입을 사용할 수 있습니다. 반환하는 함수의 반환 화살표 (->) 바로 뒤에 완전한 함수 타입을 작성하여 이를 수행합니다.
다음 예제는 stepForward(_:)와 stepBackward(_:)라 불리는 간단한 2개의 함수를 정의합니다. stepForward(_:) 함수는 입력값에 1을 더해 반환하고 stepBackward(_:) 함수는 입력값에 1을 빼고 반환합니다. 두 함수 모두 (Int) -> Int 타입 입니다.
func stepForward(_ input: Int) -> Int {
return input + 1
}
func stepBackward(_ input: Int) -> Int {
return input - 1
}
아래 함수 chooseStepFunction(backward:)는 반환 타입이 (Int) -> Int 입니다. chooseStepFunction(backward:) 함수는 backward 부울 파라미터를 토대로 stepForward(_:) 함수 또는 stepBackward(_:) 함수를 반환합니다.
func chooseStepFunction(backward: Bool) -> (Int) -> Int {
return backward ? stepBackward : stepForward
}
위의 예시에서 currentValue > 0 이 true를 반환한다는 의미이고, chooseStepFunction(backward:)가 stepBackward(_:) 함수를 반환하도록 합니다. 반환된 함수의 참조는 moveNearerToZero 라는 상수에 저장됩니다.
moveNearerToZero 가 올바른 함수를 참조하므로 0으로 계산하는데 사용할 수 있습니다.
print("Counting to zero:")
// Counting to zero:
while currentValue != 0 {
print("\(currentValue)... ")
currentValue = moveNearerToZero(currentValue)
}
print("zero!")
// 3...
// 2...
// 1...
// zero!
중첩 함수 (Nested Functions)
위에서 다룬 모든 함수는 전역 범위에서 정의된 전역 함수 (global functions) 였습니다. 반면, 중첩 함수(nested functions) 라고 하는 다른 함수 내에 함수를 정의할 수도 있습니다.
중첩 함수는 기본적으로 바깥에서 보이지 않지만 중첩 함수를 둘러싼 함수를 통해 호출될 수 있고 사용할 수 있습니다. 중첩 함수를 둘러싼 함수는 중첩 함수 중 하나를 반환하여 중첩 함수를 다른 범위에서 사용할 수도 있습니다.
위의 예시 chooseStepFunction(backward:)를 중첩 함수를 사용하고 반환하도록 아래처럼 다시 작성할 수 있습니다.
func chooseStepFunction(backward: Bool) -> (Int) -> Int {
func stepForward(input: Int) -> Int { return input + 1 }
func stepBackward(input: Int) -> Int { return input - 1 }
return backward ? stepBackward : stepForward
}
var currentValue = -4
let moveNearerToZero = chooseStepFunction(backward: currentValue > 0)
// moveNearerToZero now refers to the nested stepForward() function
while currentValue != 0 {
print("\(currentValue)... ")
currentValue = moveNearerToZero(currentValue)
}
print("zero!")
// -4...
// -3...
// -2...
// -1...
// zero!
https://docs.swift.org/swift-book/documentation/the-swift-programming-language/
Documentation
docs.swift.org