애플사이다의 iOS 개발 일지

[Swift Language Guide 정독 시리즈] 6. Functions 본문

Swift/영문 공식문서 뜯어보기-Swift

[Swift Language Guide 정독 시리즈] 6. Functions

Applecider 2021. 10. 2. 07:01

안녕하세요. 애플사이다 입니다.

[Swift Language Guide 정독 시리즈]의 여섯 번째 챕터 Functions에 대해 정리해보겠습니다.

*Swift Language Guide를 읽어야 하는 이유는 시리즈 0. Language Guide란? 포스팅을 참고해주세요.

 

이번 챕터는 제가 함수 개념을 처음 접했을 때 헷갈렸던 내용을 중점적으로 다루었습니다.

 


Functions (함수)

함수는 특정 작업을 수행하는 코드 덩어리 (chunks of code)이다.

 

Swift의 함수는 C와 같이 매개변수 (parameter) 이름이 없는 간단한 함수부터 Object-C와 같이 여러 매개변수와 전달인자 레이블 (argument label)을 가진 복잡한 형태의 함수까지 다양한 함수를 나타낼 수 있는 유연한 문법이다. 매개변수 기본값 (default values)을 제공하여 함수 호출을 단순화할 수 있고, in-out 매개변수를 통해 함수 실행을 완료하면 전달된 변수를 수정할 수 있다.

Defining and Calling Functions (함수 정의 및 호출)

함수를 정의할 때, 함수의 입력값 (input)이 되는 매개변수 (parameter)를 정의한다. 매개변수는 없어도 되고, 1개 이상이면 타입을 명시해야 한다. 또한 함수의 실행결과가 반영된 출력값 (output)이 되는 반환 타입 (return type)을 정의한다. 반환 타입은 없거나 한 가지 타입 (또는 여러 가지 값의 튜플 타입)이다. 

 

✅ 모든 함수는 이름을 가지며, 함수 이름을 통해 함수가 수행할 작업을 설명한다.

함수를 사용할 때는 함수 이름을 호출 (call)한다. 또한 매개변수에 매치 (match)되는 입력값인 전달인자 (argument)를 전달한다. 전달인자는 함수의 매개변수 리스트 (parameter list) 상의 순서와 동일하게 입력해야 한다.

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 greetAgain(person: String) -> String {
    return "Hello again, " + person + "!" // 한 줄로 축약 가능
}
print(greetAgain(person: "Anna")) // Prints "Hello again, Anna!"
  • 함수 이름 "greet"는 함수가 수행할 기능을 나타낸다. 사람 이름을 입력값으로 받아서 해당 사람에게 인사를 하는 기능이다. 
  • 함수의 타입은 매개변수 타입 및 반환 타입으로 구성된다. greet 함수의 타입은 (String) -> String 이다.
  • ❗️함수를 호출할 때는 함수의 전달인자 레이블 (argument label) 뒤에 매개변수 타입의 값을 전달한다.
    *"매개변수 이름 뒤"가 아니라 "전달인자 레이블 뒤"이다. 함수 선언 시, 전달인자 레이블을 따로 정의하지 않으면, default로 매개변수 이름을 전달인자 레이블로 사용한다.
  • 함수의 반환 타입이 String 이므로 greet 함수를 print 함수 내부에서 호출할 수 있다.

Note: 예시의 print(_:separator:terminator:) 함수가 전달인자 매개변수를 가지지 않는 이유는 print 함수 관련 포스트를 참고해주세요.

Function Parameters and Return Values (함수 매개변수 및 반환값)

Functions Without Parameters (매개변수가 없는 함수)

조건에 따른 변동 없이 모든 상황에서 동일하게 작동하는 유틸리티 함수 (utility function)를 정의할 수 있다.

func sayHelloWorld() -> String {
    return "hello, world"
}
print(sayHelloWorld()) // Prints "hello, world"
  • 항상 동일한 String 값을 출력한다.
  • 매개변수가 없어도 함수를 정의 및 호출할 때, 괄호 (parentheses, () )를 붙여야 한다.

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)를 받아서 String 타입의 적절한 인사말을 출력한다.
  • greet(person:alreadyGreeted:) 함수의 타입은 (String, Bool) -> String 이다.
  • *앞 섹션의 greet(person:) 함수와 비교하면, 함수의 이름은 동일하지만 타입이 다르다. 따라서 두 함수는 다른 함수이다.

❗️즉, 동일한 함수란?
1) 함수 이름, 2) 전달인자 레이블 (매개변수 이름이 아님), 3) 매개변수의 개수 및 타입, 4) 반환 타입, 5) 매개변수 기본값 유무가 모두 동일하다는 의미이다.

 

참고 - Swift의 함수는 재정의 (Override) 및 중복정의 (Overload)를 지원한다. 위와 같이 함수의 이름은 동일하지만, 타입이 다른 경우를 중복정의라고 한다.

Functions Without Return Values (반환값이 없는 함수)

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

print(greet(person: "TEST-1")) // Prints "Hello, TEST-1!", () 

let what = print(greet(person: "TEST-2")) // Prints "TEST-2!", () - 할당과 동시에 출력됨
print(type(of: what))  // Prints () - 타입 출력 결과
  • greet(person:) 함수는 반환값이 없다. (출력은 반환과 다르다.) 따라서 함수 정의 시 반환 타입을 명시하지 않는다.
  • greet(person:) 함수의 타입은 (String) -> Void 이다.
  • 함수의 전달인자에 "TEST-1"를 전달하고, 함수를 호출 및 출력한 결과, ()가 출력된다. 이유는 아래와 같다.
Note: Strictly speaking, this version of the greet(person:) function does still return a value, even though no return value is defined. Functions without a defined return type return a special value of type Void. This is simply an empty tuple, which is written as ().

 

✅ 엄밀히는, 반환 타입을 정의하지 않은 함수는 "Void 타입의 특정한 값"인 빈 튜플, ()을 반환한다.

 

❗️함수의 반환값을 밑줄 (underscore, _ )로 나타낸 상수에 할당하면, 반환값은 무시된다.

참고 - @discardableResult 선언 속성을 사용하면, 반환값을 사용하지 않아도 컴파일러가 경고 메시지를 표시하지 않는다.

func printAndCount(string: String) -> Int {
    print(string)
    return string.count
}
printAndCount(string: "hello, world") // prints "hello, world" and returns a value of 12 - 반환값이 있음

func printWithoutCounting(string: String) {
    let _ = printAndCount(string: string)
}
printWithoutCounting(string: "hello, world") // prints "hello, world" but doesn't return a value
// 반환값을 밑줄(_)로 나타낸 상수에 할당하여 무시됨
  • 첫 번째 함수 printAndCount(string:)는 문자열을 출력하고, Int 값을 반환한다.
    (단, 반환값을 상수/변수에 할당하지 않아 반환값에 접근할 수 없다.)
  • 두 번째 함수 printWithoutCounting(string:)는 첫 번째 함수를 호출하지만, 반환값은 무시한다.
    따라서 두 번째 함수를 호출했을 때, 문자열은 출력되지만 반환값을 사용하지 않는다.
Note: Return values can be ignored, but a function that says it will return a value must always do so. A function with a defined return type can’t allow control to fall out of the bottom of the function without returning a value, and attempting to do so will result in a compile-time error.

 

반환값은 무시될 수 있지만, 반환 타입이 있는 함수는 항상 값을 반환한다.
❗️반환 타입을 정의한 함수는 반환값을 반환하기 전에 함수에서 빠져나갈 수 없다. 반환값을 반환하기 전에 함수를 종료하면, 컴파일 에러가 발생한다.

Functions with Multiple Return Values (반환값이 여러 개인 함수)

반환값이 여러 개라면, 함수의 반환 타입으로 튜플 (tuple)을 사용한다. 

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"
  • minMax(array:) 함수는 Int 타입의 Array를 전달받고, Array의 item 중에서 최소값/최대값을 찾아 튜플로 반환한다.
  • 반환값의 튜플 요소는 min/max로레이블 했다. 함수의 반환값에 접근할 때 해당 레이블을 사용할 수 있다. (dot syntax)

Optional Tuple Return Types (옵셔널 튜플 반환 타입)

함수의 반환 타입이 튜플인데, 전체 튜플이 값을 가지지 않을 가능성이 있는 경우가 있다. 이때는 반환 타입을 "옵셔널 튜플 타입"으로 한다.

즉, 튜플 전체가 nil 일 가능성이 있음을 명시하는 것이다. 예를 들어 (Int, Int)? 로 나타낸다.

 

Note: 1) (Int, Int)? 타입은 2) (Int?, Int?) 타입과 다르다. 1) 첫 번째는 전체 튜플이 옵셔널 타입이라는 것이고, 2) 두 번째는 옵셔널이 아닌 튜플 내부에 옵셔널 값이 들어있음을 의미한다.

 

위 예시의 minMax(array:) 함수는 안전성 확인 (safety checks)을 하지 않았다. 전달인자로 받은 Array가 비어 있다면, array[0] 형태로 접근했을 때, 런타임 에러가 발생한다. 따라서 옵셔널 튜플 반환 타입을 사용한 아래의 예시가 더 안전하다.

func minMax(array: [Int]) -> (min: Int, max: Int)? { // 반환 타입을 옵셔널 튜플로 지정
    if array.isEmpty { return nil } // safety check 추가
    
    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"
  • 전달인자로 빈 Array가 전달되면, nil을 반환한다.

Functions With an Implicit Return (암시적 반환을 사용하는 함수)

1) 함수 내부의 코드가 단 한 줄 (single expression)이고, 2) 해당 수식의 결과값이 함수의 반환 타입과 일치하면, 함수는 암시적으로 해당 수식의 결과를 반환한다.

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

프로퍼티의 getter 또한 암시적 반환을 사용할 수 있다.

*getter의 암시적 반환은 Properties 챕터의 Shorthand Setter Declaration 섹션에서 자세히 다루겠습니다.

 

Note: 암시적 반환을 사용하려면, 함수의 반환 타입을 정의해야 한다. 예를 들어 fatalError("Oh no!") 또는 print(13) 를 암시적 반환값으로 사용할 수 없다. (fatalError 함수, print 함수는 반환 타입이 없기 때문이다.)

Function Argument Labels and Parameter Names (함수의 전달인자 레이블 및 매개변수 이름) 

모든 함수의 매개변수는 1) 매개변수 전달인자 (argument label), 2) 매개변수 이름 (parameter name)을 가진다.

❗️매개변수 전달인자를 별도로 지정하지 않으면, default로 매개변수 이름을 매개변수 전달인자로 사용한다.

  • 매개변수 전달인자는 함수를 호출할 때 사용한다. (함수 호출 시, 전달인자 앞에 있는 것이 매개변수 전달인자이다.)
  • 매개변수 이름은 함수 내부에서 코드를 실행할 때 사용한다.
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 checkParameters(label a: Int, label b: Int, label a: Int) { } // 컴파일 에러 - Invalid redeclaration of 'a'

func checkParameters(label a: Int, label b: Int, label c: Int) { } // 가능
func checkParameters(differentLabel a: Int, label b: Int, label c: Int) { } // 가능 - *함수 이름은 같지만, 전달인자 레이블이 다르므로 중복정의 가능
func checkParameters(label differentA: Int, label b: Int, label c: Int) { } // 컴파일 에러 - Invalid redeclaration of 'checkParameters(label:label:label:)'

func checkParameters(label a: Int, label b: Int, label c: Int = 10) { } // 가능 - 매개변수 기본값이 있으면, 중복정의 가능
func checkParameters(label a: Int, label b: Int, label c: Int = 20) { } // 컴파일 에러 - Invalid redeclaration of 'checkParameters(label:label:label:)'
  • 첫 번째 함수 : 매개변수 이름 (a, b, a)이 중복되므로 컴파일 에러가 발생한다. 
  • 두 번째 함수 : 전달인자 레이블은 중복 가능하다.
  • 세 번째 함수 : ✅ 두 번째 함수와 매개변수 이름 (a, b, c)이 동일하지만, 전달인자 레이블이 다르므로 중복정의가 가능하다.
  • 네 번째 함수 : ✅ 두 번째 함수와 매개변수 이름 (differentA, b, c)이 다르지만, 전달인자 레이블이 동일하므로 컴파일 에러가 발생한다.
  • 다섯 번째 함수 : ✅ 두 번째 함수와 전달인자 레이블이 동일하지만, 매개변수 기본값이 있으므로 중복정의가 가능하다. (아래 섹션 참고)
  • 여섯 번째 함수 : 다섯 번째 함수와 매개변수 기본값의 수치가 다르지만, 중복정의가 불가하다.

Specifying Argument Labels (전달인자 레이블 지정)

전달인자 레이블을 사용하면, 함수 외부에서 매개변수의 역할을 보다 명확히 할 수 있다.

또한 영어로 된 문장을 표현 (sentence-like manner)하는 것처럼 함수를 가독성 있게 사용할 수 있다.

 

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

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

*전달인자 레이블의 사용 방법은 API Design Guidelines 정독 시리즈의 Naming 파트에서 자세히 다루겠습니다.

 

x.insert(y, at: z)          “x, insert y at z” (x에 y를 z위치에 삽입)  // 좋은 예시
x.subViews(havingColor: y)  “x's subviews having color y” (x의 subviews는 색상 y을 가짐)
x.capitalizingNouns()       “x, capitalizing nouns” (x의 명사를 대문자화)

x.insert(y, position: z)  // 나쁜 예시
x.subViews(color: y)
x.nounCapitalize()

Omitting Argument Labels (전달인자 레이블 생략)

밑줄 (underscore, _ )을 사용하면, 전달인자 레이블을 생략할 수 있다.

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 (매개변수 기본값)

함수를 정의할 때, 함수의 매개변수에 기본값을 지정할 수 있다. 기본값이 있는 매개변수는 함수를 호출할 때, 해당 매개변수를 생략 가능하다. 또한 매개변수 기본값이 있는 함수는 중복 정의가 가능하다.

✅ 이때, 기본값이 없는 매개변수를 매개변수 리스트의 앞에 배치한다. 기본값이 있는 매개변수는 보통 중요성이 낮기 때문이다.

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 Parameters (가변 매개변수)

매개변수로 몇 개의 값이 들어올지 정확히 알기 어려울 때 사용한다.

가변 매개변수는 0개 이상의 값을 전달받는다. 전달받은 매개변수는 함수 내부에서 Array로 사용된다.

함수는 여러 개의 가변 매개변수를 사용할 수 있다. (Swift 버전 5.4에서 업데이트됨) 단, 가변 매개변수 바로 뒤에 위치한 매개변수는 반드시 매개변수 레이블을 가져야 한다. 

func arithmeticMean(_ numbers: Double...) -> Double {
    var total: Double = 0
    for number in numbers { // 가변 매개변수의 전달인자는 [Double] 타입의 Array로 사용됨
        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

print(arithmeticMean()) // nan 출력
  • ✅ 마지막 줄 : 가변 매개변수에 전달인자를 전달하지 않으면, nan이 출력된다. nan은 "숫자가 아님"이라는 뜻이다.
func arrayParameter(label numbers: [Double]) -> String { // Array 타입 매개변수
    return "\(numbers)"
}

func variadicParameter(label numbers: Double...) -> String { // 가변 매개변수
    return "\(numbers)"
}

print(arrayParameter(label: [1, 2, 3]))  // [1.0, 2.0, 3.0]
print(variadicParameter(label: 1, 2, 3)) // [1.0, 2.0, 3.0]

print(arrayParameter(label: []))  // []
print(variadicParameter(label: )) // (Function) - 타입 자체가 출력됨

print(arrayParameter())    // 컴파일 에러 - Array는 전달인자 레이블 생략 불가
print(variadicParameter()) // [] - 가변 매개변수는 전달인자 레이블 생략 가능
  • Array 타입 매개변수 및 가변 매개변수를 비교하는 코드이다.
  • ❗️마지막 줄 : Array 타입 매개변수는 전달인자 레이블을 생략 불가하므로 컴파일 에러가 발생한다.
    (Missing argument for parameter 'label' in call)
    반면, 가변 매개변수는 전달인자 레이블을 생략 가능하다.

In-Out Parameters (입출력 매개변수)

❗️ 함수의 전달인자로 값을 전달할 때, 보통 값을 복사하여 전달한다. 이때 값이 아니라 참조 (Reference)를 전달하려면 입출력 매개변수를 사용한다. (구조체, 열거형은 값 타입이고, 클래스는 참조 타입이다. 또한 함수 및 클로저는 참조 타입이다.)

단, 객체지향 프로그래밍 패러다임을 사용하는 애플 프레임워크에서는 유용하지만, 외부 환경에서 함수형 프로그램을 사용할 때는 지양하는 패턴이다. (함수 외부의 값에 어떤 영향을 미칠지 알 수 없기 때문이다.) 또한 메모리 안전에 신경 써야 한다.

 

❗️ 함수의 매개변수는 default로 상수 (constants)이다. 매개변수로 받은 값을 해당 함수 내부에서 변경하면, 컴파일 에러가 발생한다.

입출력 매개변수를 사용하면, 1) 매개변수로 받은 값을 변경하거나, 2) 함수 실행이 종료된 이후에도 변경된 값을 사용할 수 있다.

 

입출력 매개변수로 변수만 전달 가능하다. 상수나 리터럴값 (literal value)은 변경할 수 없기 때문이다.

입출력 매개변수로 전달할 변수이름 앞에 앰퍼샌드 (ampersand, &)를 붙인다.

 

자세한 동작 방식이나 컴파일러 최적화에 대한 내용은 In-Out Parameters에서 확인 가능하다.

참고 - 입출력 매개변수는 아래와 같이 동작한다. 이 동작 방식을 copy-in copy-out 또는 call by value result라고 한다.

1. 함수를 호출하면, 전달인자의 값이 복사된다.

2. 함수 내부에서 값이 변경된다.

3. 함수를 종료할 때, 변경된 값이 기존 전달인자에 할당된다.

 

Note: 입출력 매개변수는 기본값을 가질 수 없고, 가변 매개변수로 사용할 수 없다.

func swapTwoInts(_ a: inout Int, _ b: inout Int) { // 입출력 매개변수 a, b
    let temporaryA = a // a와 b의 값을 swap하는 기능
    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"

Note: 입출력 매개변수는 함수의 반환값과 다르다. 위 예시의 swapTwoInts 함수는 반환 타입이 없다. 

입출력 매개변수는 반환값 없이 함수 외부에 영향을 미치는 또 다른 방법이다.

*입출력 매개변수가 아니면, 함수 종료 시 전달인자 및 지역변수가 Stack에서 pop되므로 함수 외부에 영향을 미치지 않는다.

Function Types (타입으로서의 함수)

Swift의 모든 함수는 타입을 가진다. 함수의 타입은 매개변수 타입 및 반환 타입으로 구성된다.

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")
}
  • 첫 번째, 두 번째 함수 : 모두 (Int, Int) -> Int 타입이다.
  • 세 번째 함수 : 매개변수도 없고, 반환타입도 없다. () -> Void 타입이다.

매개변수가 없고, 반환타입이 없는 함수의 타입은 아래처럼 표현한다. (모두 동일함)

  • (Void) -> Void
  • () -> ()
  • () -> Void

Using Function Types (타입으로서의 함수 사용)

Swift의 함수는 일급 객체이다. 즉, Swift의 모든 다른 타입처럼 함수 타입을 사용할 수 있다.

예를 들어 1) 상수/변수의 타입을 함수로 지정하거나, 2) 함수를 다른 함수의 매개변수로 전달하거나, 3) 함수가 함수를 반환할 수 있다. 또한 함수를 다른 함수 안에 작성하여 중첩 함수 형태로 사용할 수 있다.

var mathFunction: (Int, Int) -> Int = addTwoInts
print("Result: \(mathFunction(2, 3))") // Prints "Result: 5"

mathFunction = multiplyTwoInts
print("Result: \(mathFunction(2, 3))") // Prints "Result: 6"

let anotherMathFunction = addTwoInts
// anotherMathFunction is inferred to be of type (Int, Int) -> Int
  • 첫 번째 줄 : 변수 mathFunction가 함수 addTwoInts를 참조 (refer)하도록 설정한다. (Set this new variable to refer to the function called addTwoInts.)
  • 세 번째 줄 : 변수 mathFunction, 함수 addTwoInts, 함수 multiplyTwoInts의 타입은 모두 (Int, Int) -> Int 로 동일하므로 해당 변수에 동일한 타입의 함수를 할당 가능하다.

Function Types as Parameter Types (매개변수 타입으로서의 함수 타입)

✅ Swift의 함수는 일급 객체이므로 함수의 매개변수 타입을 (Int, Int) -> Int 등의 함수 타입으로 지정할 수 있다.

이렇게 하면 함수 구현의 일부분을 함수 호출자 (function’s caller)가 함수를 호출할 때 결정할 수 있도록 남겨둘 수 있다.

 

단. 전달인자 레이블은 함수 타입의 구성요소가 아니므로 함수 타입을 지정할 때, 전달인자 레이블을 사용하지 않는다.

func printMathResult(_ mathFunction: (Int, Int) -> Int, _ a: Int, _ b: Int) { // 매개변수 타입이 함수 타입
    print("Result: \(mathFunction(a, b))")
}
printMathResult(addTwoInts, 3, 5) // Prints "Result: 8"
  • printMathResult 함수가 호출되면, addTwoInts 함수 및 3, 5가 전달인자로 전달되고, 전달받은 함수 addTwoInts를 3, 5로 호출하여 결과적으로 8을 출력한다.

Function Types as Return Types (반환 타입으로서의 함수 타입)

함수의 반환 타입을 함수 타입으로 지정할 수 있다.

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

print("Counting to zero:") // Counting to zero:
while currentValue != 0 {
    print("\(currentValue)... ")
    currentValue = moveNearerToZero(currentValue)
}
print("zero!")
// 3...
// 2...
// 1...
// zero!
  • 함수 chooseStepFunction의 반환 타입은 (Int) -> Int 이다. 전달인자 backward의 값에 따라 실행하는 함수가 결정된다.

Nested Functions (중첩 함수)

중첩 함수는 함수의 사용 범위를 명확하게 표현하고 싶을 때 사용한다.

전역 함수 (global functions)가 아니라 특정 함수 내부에서만 사용할 함수를 중첩 함수로 정의한다.

 

✅ 단, 중첩 함수를 감싸고 있는 함수 (enclosing function)의 내부에서만 중첩 함수를 사용 가능한 것은 아니다.

enclosing function는 중첩 함수를 호출하거나 반환할 수 있다. 이렇게 반환된 함수는 enclosing 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!

참고 - 비반환 함수 (Nonreturning Function)

정상적으로 종료되지 않는 함수를 의미한다. 오류를 던지거나, 중대한 시스템 오류를 보고할 때 사용한다. 오류를 보고한 이후에 프로세스를 종료한다.

반환 타입을 Never로 명시하며, 어디서든 호출 가능하다. (guard문의 else 블록에서도 호출 가능하다.)

func crashAndBurn() -> Never {
    fatalError("Something bad happened.")
}
crashAndBurn() // 에러 메시지 - Fatal error: Something bad happened.

참고 - 반환값을 무시 가능한 함수 (종료되지 않는 함수)

함수 선언 시 반환값이 있다고 선언했지만, 반환값이 필요 없는 상황에서 사용한다.

@discardableResult 선언 속성을 사용한다.

func greeting(_ name: String) -> String {
    print("Hello, \(name)")
    return name
}
@discardableResult func discardableResultGreeting(_ name: String) -> String {
    print("Hello, \(name)")
    return name
}
greeting("AppleCider") // 반환값을 사용하지 않았으므로 컴파일러 경고가 나타남
discardableResultGreeting("AppleCider") // 반환값을 사용하지 않아도 컴파일러 경고가 나타나지 않음

 

- Reference

  • Swift Language Guide > Functions
  • Swift Language Reference > Declarations > In-Out Parameters
  • Apple Developer Documentation > nan
  • 책 <스위프트 프로그래밍>, 야곰

 

🍎 포스트가 도움이 되었다면, 공감🤍 / 구독🍹 / 공유🔗 / 댓글✏️ 로 응원해주세요. 감사합니다.

Comments