공식 문서로 공부하는 Swift (5) - 함수

ci·2020년 5월 25일
1

Functions

함수(Function)은 특정 기능을 실행하기 위한 코드의 집합이다. 함수가 어떤 일을 하는지 식별하기 위해 이름을 지어 줄 수 있다. 이 이름을 통해 필요한 시점에 함수를 호출해 실행할 수 있다.

인자와 반환값을 포함하여, Swift의 모든 함수는 타입을 가진다. 이러한 타입을 Swift의 다른 타입들처럼 사용할 수 있다. 다른 함수에 인자로 들어갈 수 있고, 다른 함수로부터 함수를 반환받을 수도 있다.


함수의 정의 및 호출

함수를 정의할 때, 선택적으로 하나 이상의 매개 변수와 반환 타입을 정의할 수 있다.

모든 함수는 이름을 갖고 있으며, 이를 통해 함수를 호출하고 매개 변수의 타입에 맞는 값을 넣을 수 있다. 함수의 인수는 반드시 매개 변수와 같은 순서로 제공되어야 한다.

func greet(person: String) -> String {
    let greeting = "Hello, " + person + "!"
    return greeting
}

print(greet(person: "Anna"))
// Prints "Hello, Anna!"
print(greet(person: "Brian"))
// Prints "Hello, Brian!"

함수를 선언할 때는 가장 앞에 func 키워드를 붙이고 (person: String) 파라미터와 형 그리고 -> String 형태로 반환형을 정의한다.

이러한 정의는 함수가 무엇을 하는지, 어떤 것을 받게 되는지, 수행되었을 때 무엇을 반환하는지를 기술한다.

print(_:separator:terminator:) 함수는 첫 번째 인자에 라벨이 존재하지 않는다. 또한 다른 인자들은 기본 값이 존재하기 때문에 선택적이다.



함수의 매개 변수와 반환 값

Swift의 매개 변수는 극단적으로 융통성이 있다. 이름 없는 매개 변수 하나를 쓰는 간단한 함수부터 복잡한 함수까지 무엇이든 정의할 수 있다.


매개 변수가 없는 함수

함수는 매개 변수를 반드시 필요로 하지는 않는다.

func sayHelloWorld() -> String {
    return "hello, world"
}
print(sayHelloWorld())
// Prints "hello, world"

매개 변수가 있는 함수

함수는 소괄호 안에 쓰여지는 여러 개의 매개 변수를 가질 수 있다. 매개 변수는 콤마로 분리된다.

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!"

반환 값이 없는 함수

함수는 반환 값을 반드시 필요로 하지는 않는다.

func greet(person: String) {
    print("Hello, \(person)!")
}
greet(person: "Dave")
// Prints "Hello, Dave!"

반환 값이 필요 없기 때문에, 함수의 정의는 반환 화살표(->)나 반환 타입을 포함하지 않는다.

명백히 말하자면, greet(person:) 함수는 반환 값이 정의되지 않았음에도 불구하고 값을 반환한다. 정의된 반환 값이 없는 함수는 Void 타입이라는 특수한 값을 반환한다. 간단히 말하자면 이것은 빈 튜플이며, ()로 표현된다.


호출할 때 반환 값을 무시할 수도 있다.

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

반환 값이 무시되었음에도 함수는 항상 값을 반환한다. 정의된 반환 타입이 있는 함수는 반환 값이 없는 상태를 허용하지 않는다. 컴파일 시간대에 에러를 발생시킨다.


여러 값을 반환하는 함수

튜플을 사용해 여러가지 값을 묶어서 반환할 수 있다.

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)
}

튜플의 원소는 함수 반환 타입의 일부분으로써 이름이 붙여지기 때문에, 점 문법(.)을 사용해 각각의 튜플 값에 접근할 수 있다.

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"

함수의 반환 값 타입의 일부분으로써 이미 이름이 정해졌기 때문에, 튜플의 원소는 함수가 반환될 때 이름을 붙여 줄 필요가 없다.


옵셔널 튜플 반환 타입

만약 함수로부터 반환되는 튜플이 값을 갖지 않을 가능성이 있을 경우, 옵셔널로 반환 타입을 선언할 수 있다. 전체 튜플이 nil이 될 수 있는 것이다. 튜플의 닫는 소괄호 뒤에 물음표를 붙여준다.

(Int, Int)?(Int?, Int?)는 엄연히 다르다. 전자는 튜플이 옵셔널인 것이고, 후자는 튜플 안의 원소가 옵셔널인 것이다.

위의 minMax(array:) 함수는 만약 인자로 빈 배열이 들어온다면 런타임 에러가 발생할 것이다. 따라서 옵셔널 튜플 타입을 반환하도록 수정한다.

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)
}

옵셔널 바인딩을 사용할 수도 있다.

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"


암시적 반환을 하는 함수

만약 전체 표현식이 한 줄이라면, 그 표현식을 반환할 수 있다.

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!"


함수 인자 라벨과 매개 변수 이름

함수의 각 매개 변수는 인자 라벨매개 변수 이름을 갖고 있다. 인자 라벨은 주로 함수를 호출할 때 이용된다. 매개 변수 이름은 함수의 구현에 사용된다. 인자 라벨의 기본 값은 매개 변수 이름이다.

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)

모든 매개 변수는 반드시 고유한 이름을 가져야 한다. 서로 다른 매개 변수가 동일한 라벨을 가질 수 있긴 하지만, 고유한 인자 라벨을 붙이는 것이 가독성 측면에 도움이 될 것이다.


인자 라벨 설정

매개 변수 이름 앞에 인자 라벨을 작성한다.

func someFunction(argumentLabel parameterName: Int) {
    // In the function body, parameterName refers to the argument value
    // for that parameter.
}

인자 라벨 생략

매개 변수의 인자 라벨을 사용하고 싶지 않을 경우, 언더스코어(_)를 매개 변수 앞에 작성하여 생략할 수 있다.

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)

만약 매개 변수가 인자 라벨을 갖고 있다면, 함수를 호출할 때 반드시 인자를 라벨로 표기해야 한다.


매개 변수의 기본 값

함수의 매개 변수를 정의할 때, 매개 변수 타입 다음에 기본 값을 설정할 수 있다. 기본 값을 설정했다면 함수를 호출할 때 매개 변수를 생략할 수 있다.

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

보통 기본 값을 설정하지 않는 매개 변수는 함수에게 더 중요한 의미를 가진다.


매개 변수 집합

매개 변수 집합은 0개 이상의 값을 허용한다. 함수가 호출될 때 변화하는 입력 값을 받을 수 있을 때 매개 변수 집합을 사용한다. 매개 변수 타입 뒤에 온점 셋(...)을 붙임으로써 작성한다.

매개 변수 집합으로 들어온 값들은 함수 내에서 적당한 타입의 배열로 만들어진다.

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

인-아웃 매개 변수

함수의 매개 변수는 기본적으로 상수다. 함수 안에서 매개 변수를 바꾸려고 할 경우, 컴파일 에러가 발생한다. 이는 실수에 의해 매개 변수의 값이 바뀌는 것을 방지해 준다. 만약 매개 변수의 값을 함수에서 수정하고, 호출이 끝난 뒤에도 수정 값을 유지하고 싶다면 인-아웃 매개 변수로 매개 변수를 정의한다.

매개 변수 타입 앞에 inout 키워드를 덧붙임으로써 인-아웃 매개 변수를 작성한다. 인-아웃 매개 변수는 함수 안에서 수정될 수 있는 값을 갖는다.

오직 변수만 인-아웃 매개 변수에 넣을 수 있으며, 상수나 리터럴 값은 불가능하다. 함수 호출 시 매개 변수 앞에 & 기호를 붙임으로써 이 매개 변수가 함수 안에서 수정될 수 있음을 표시한다.

func swapTwoInts(_ a: inout Int, _ b: inout Int) {
    let temporaryA = a
    a = b
    b = temporaryA
}

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"

인-아웃 매개 변수는 기본 값을 가질 수 없다. 또한 매개 변수 집합은 inout 키워드로 선언될 수 없다.



함수의 타입

모든 함수는 매개 변수 또는 반환 값 타입으로 사용될 수 있는 특정한 타입을 가진다.

func addTwoInts(_ a: Int, _ b: Int) -> Int {
    return a + b
}
func multiplyTwoInts(_ a: Int, _ b: Int) -> Int {
    return a * b
}

위 두 함수는 모두 두 개의 Int 타입 매개 변수를 인자로 받아 Int 값을 반환한다.

이 함수들의 타입은 (Int, Int) -> Int가 된다.


func printHelloWorld() {
    print("hello, world")
}

이 함수의 타입은 () -> Void이다.


함수 타입 사용하기

함수 타입을 다른 타입과 같이 사용할 수 있다. 예를 들어, 함수 타입으로 상수 또는 변수를 정의하고 적당한 함수를 할당할 수 있다.

var mathFunction: (Int, Int) -> Int = addTwoInts

addTwoInts(_:_:) 함수는 mathFunction과 같은 타입이기 때문에 Swift의 타입 체커에 의해 할당이 허용된다.

이제 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

매개 변수로 사용되는 함수 타입

(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"

반환 타입으로 사용되는 함수 타입

다른 함수의 반환 타입으로써 함수 타입을 사용할 수 있다. 함수 타입을 화살표(->) 뒤에 작성함으로써 사용한다.

func stepForward(_ input: Int) -> Int {
    return input + 1
}

func stepBackward(_ input: Int) -> Int {
    return input - 1
}

func chooseStepFunction(backward: Bool) -> (Int) -> Int {
    return backward ? stepBackward : stepForward
}

var currentValue = 3
let moveNearerToZero = chooseStepFunction(backward: currentValue > 0)
// moveNearerToZero now refers to the stepBackward() function


중첩 함수

지금까지의 모든 함수는 전역 범위에서 정의된 전역 함수였다. 함수 안에 다른 함수를 선언할 수도 있는데, 이를 중첩 함수라 부른다.

중첩 함수는 바깥으로부터 숨겨져 있으며, 인접한 함수에서만 호출되고 사용될 수 있다. 인접 함수는 그의 중첩 함수 중 하나를 다른 범위에서 사용될 수 있게 만들기 위해 반환할 수 있다.

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!

0개의 댓글