[Swift] 기초 문법 정리 - 함수 (1)

Posted by WhiteHyun on March 18, 2022

Goal

  • Swift의 함수에 대한 정의를 올바르게 이해한다.
  • 함수를 직접 사용할 수 있다.

들어가기에 앞서

  1. 본 글은 Swift의 언어가이드를 따릅니다.[1]
  2. 문법작성규칙은 아래와 같이 작성합니다.[2]
    • lower camel case: method, variable, constant
    • upper camel case: enum, struct, class, extension

함수란?

  • 함수는 여러분들이 잘 아시다시피 특정 작업을 수행하는 코드의 집합입니다.
  • Swift의 함수는 C-Style 함수에서 각 parameter와 argument label이 있는 복잡한 objective-C-Style에 이르기까지 모든 표현을 할 수 있을 정도로 유연합니다.
  • 함수는 파라미터 유형과 반환타입으로 구성된 타입을 가집니다. 따라서 함수를 파라미터로 전달할 수도 있으며 함수에서 함수를 반환하는 것이 가능합니다.

함수의 선언 및 호출

  • Swift에서 함수는 이름parameter, return type으로 구성되어있습니다.
  • 함수는 func 키워드가 앞에 붙어 사용되며 return type은 ->(하이픈 뒤에 오른쪽 꺾쇠 괄호)로 지정합니다. 이 뒤에 반환할 타입을 작성합니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    // hello라는 함수에 name이라는 parameter로 String을 받고 반환값은 없습니다.
    func hello(name: String) {
        print("Hello, \(name)!")
    }
    
    func greet(person: String) -> String {
        let greeting = "Hello, " + person + "!"
        return greeting
    }
    
  • 함수를 호출하고 싶으면 함수 이름 뒤에 괄호를 사용하여 파라미터를 전달합니다.
  • greet(person:) 함수는 greet(person: “Taylor”)와 같이 person이라는 인자에 String값을 실어서 호출합니다. 이 함수는 String 값을 반환하기 때문에 greet(person:)을 print(_: separator: terminator:) 함수에 대한 호출로 랩하여 위와 같이 해당 문자열을 프린트하여 반환값을 확인할 수 있습니다.

    1
    2
    3
    4
    
    print(greet(person: "Taylor"))
    // Prints "Hello, Taylor!"
    print(greet(person: "Brian"))
    // Prints "Hello, Brian!"
    

    NOTE 🧑‍💻:

    print(_: separator: terminator:) 함수에는 첫 번째 argument label이 없습니다.또, 그 외 인자는 기본값으로 설정되어 있기 때문에, 옵션입니다. 함수 구문에 대한 이러한 변화는 다음 전달인자 레이블과 파라미터 이름파라미터 기본값에서 설명합니다.

  • greet(person:) 함수의 본문은 greeting이라는 문자열 상수를 정의하고 이를 단순한 인사 메시지로 설정합니다. 이 greeting은 return 키워드를 사용하여 함수에서 반환됩니다. return greeting이라고 하는 코드에서 함수는 실행을 종료하고 greeting의 현재 값을 반환합니다.
  • 다른 입력값을 사용하여 greet(person:) 함수를 여러 번 호출할 수 있습니다. 위의 예는 입력값 “Taylor”와 입력값 “Brian”을 사용하여 호출하면 어떻게 되는지 보여줍니다. 이 함수는 각각의 경우에 맞춤 greeting을 반환합니다.
  • 이 함수의 본문을 짧게 하기 위해 메시지 작성과 반환문을 한 줄로 조합할 수 있습니다.

    1
    2
    3
    4
    5
    
    func greetAgain(person: String) -> String {
        return "Hello again, " + person + "!"
    }
    print(greetAgain(person: "Anna"))
    // Prints "Hello again, Anna!"
    

함수 파라미터와 반환값

  • Swift에서는 함수 파라미터와 반환값이 매우 유연합니다. 이름 없는 매개 변수를 사용하는 단순 유틸리티 함수부터 표현식 매개 변수 이름 및 다른 매개 변수 옵션을 사용하는 복잡한 함수까지 모든 기능을 정의할 수 있습니다.

파라미터가 없는 함수

  • 함수는 입력 파라미터를 정의할 필요가 없습니다. 다음같이 입력 매개 변수가 없는 함수는 호출될 때마다 항상 동일한 String 메시지를 반환합니다.

    1
    2
    3
    4
    5
    
    func sayHelloWorld() -> String {
        return "hello, world"
    }
    print(sayHelloWorld())
    // Prints "hello, world"
    
  • 함수를 정의할 때에는 매개 변수가 필요하지 않더라도 함수 이름 뒤에 괄호가 반드시 필요합니다. 함수를 호출할 때에도 함수 이름 뒤에 빈 괄호 쌍을 사용합니다.

여러 파라미터가 있는 함수

  • 함수는 여러 파라미터를 가질 수 있으며, 함수의 괄호 안에 쉼표로 구분됩니다.
  • 이 함수는 이름과 이미 인사여부를 입력하고 해당 사용자에 대한 적절한 인사말을 반환합니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    func greet(person: String, alreadyGreeted: Bool) -> String {
        if alreadyGreeted {
            return greetAgain(person: person)
        } else {
            return greet(person: person)
        }
    }
    print(greet(person: "SeungHyun", alreadyGreeted: true))
    // Prints "Hello again, SeungHyun!"
    
  • greet(person: alreadyGreeted:) 함수는 괄호 안에 person이라는 이름의 String argument값과 alreadyGreeted라는 이름의 Bool argument값을 쉼표로 구분하여 전달함으로써 호출할 수 있습니다.
  • 이 함수는 이전 섹션에서 설명한 greet(person:) 함수와 다릅니다. 두 함수 모두 greet으로 시작하는 이름이 있지만 greet(person: alreadyGreeted:) 함수는 두 개의 인자를 갖지만 greet(person:) 함수는 하나만 갖고 있습니다.

반환값 없는 함수

  • 함수를 정의하는 데 반환값이 필요하지 않을 때가 있을겁니다. 다음은 greet(person:) 함수의 반환값 없는 버전입니다. 이 함수는 반환하지 않고 독자적인 String 값을 출력합니다.

    1
    2
    3
    4
    5
    
    func greet(person: String) {
        print("Hello, \(person)!")
    }
    greet(person: "Dave")
    // Prints "Hello, Dave!"
    
  • 반환값이 필요없기 때문에 반환을 표시하는 화살표(->)와 반환 타입을 생략할 수 있습니다.

    NOTE 🧑‍💻:

    엄밀히 말하면 반환값이 정의되어 있지 않아도 값을 반환합니다. 정의된 반환타입이 없는 함수는 Void 타입의 특수 값을 반환합니다. 이것은 단순히 빈 튜플이며, () 또는 Void로 표시됩니다.
    추가로 다음은 전부 같은 표현입니다.
    (Void) -> Void
    () -> Void
    () -> ()

  • 함수가 다음과 같이 호출되면 함수의 반환 값은 무시될 수 있습니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    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 doesn't return a value
    
  • 첫 번째 함수 printAndCount(string:)는 문자열을 출력한 후 Int로 문자열의 길이를 반환합니다.두 번째 함수인 printWithoutCounting(string:)은 첫 번째 함수를 호출하지만 반환값은 무시합니다. 따라서 두 번째 함수를 호출하면 첫 번째 함수에 의해 메시지가 출력되지만 반환된 값은 사용되지 않습니다.

    NOTE 🧑‍💻:

    반환 값은 무시할 수 있지만 반환값이 있다고 설정한 함수는 무조건 반환값이 있도록 구성하여야 합니다. 즉, 반환 타입이 정의된 함수가 값을 반환하지 않고 빠져나가는 것을 허용하지 않으며, 이를 시도하려 한다면 컴파일 오류가 발생합니다.

여러 반환값이 있는 함수

  • 여러 값을 반환하는 함수의 타입으로 튜플 타입을 사용할 수 있습니다.
  • 다음 예제에서는 minMax(array:)라는 함수를 정의하고 있습니다. 이 함수는 Int 값의 배열에서 가장 작은 숫자와 가장 큰 숫자를 찾습니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
    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:) 함수는 두 Int 타입의 값을 가지고 있는 튜플을 반환합니다. 튜플의 값은 min과 max로 레이블링되어있어 해당 이름으로 접근할 수 있습니다.

    1
    2
    3
    
    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"
    

    NOTE 🧑‍💻:

    해당 예제에서는 튜플 타입 내 값을 레이블링하여 접근하였지만, 이는 선택입니다.
    레이블링하지 않은 경우 반환값.0, 반환값.1 과 같이 접근가능합니다.
    튜플에 대한 설명은 여기를 참조해주세요.

옵셔널 형태의 튜플 반환타입

  • 만약 튜플 타입으로 반환하는 함수에서 “값이 없음”이 될 가능성이 있는 경우 옵셔널 타입을 추가로 선언함으로써 튜플반환값이 nil이 될 수 있다는 것을 반영할 수 있습니다.
  • 옵셔널 형태는 (Int, Int)?(String, Int, Bool)?과 같이 ?(물음표)기호를 튜플 타입의 닫는괄호 뒤에 붙여 사용합니다.

    NOTE 🧑‍💻:

    (Int, Int)? 와 (Int?, Int?)는 엄연한 차이가 있습니다.
    (Int, Int)?는 nil 이거나 튜플의 값을 가지고 있다는 의미이고
    (Int?, Int?)는 튜플타입이되, 튜플 값이 Int값이거나 nil일 수도 있다는 의미입니다.

  • 위의 minMax(array:) 함수는 두 Int 타입의 값을 가진 튜플을 반환합니다. 하지만, 함수는 전달받은 배열에 대한 안정성 검사를 수행하지 않습니다. 만약 배열의 인자가 없는 빈 배열인 경우, 해당 함수는 배열의 첫 번째 인자에 접근하기 때문에 런타임 에러가 발생할 것입니다.

  • 빈 배열에 대한 안정성을 강화하기 위해 옵셔널 튜플로 반환타입을 설정하고, 빈 배열일 경우 nil을 반환하도록 함수를 재구성하였습니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
    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 구문)[3]을 이용하여 minMax(array:) 함수가 튜플 값을 반환하는지, nil을 반환하는지 확인할 수 있습니다.

    1
    2
    3
    4
    
    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"
    

암묵적 반환을 수행하는 함수

  • 만약 함수 본문이 한 줄로만 적혀져 있는 경우, return을 제거하여도 함수는 반환타입을 암묵적으로 추론할 수 있습니다. 예를 들어, 아래 두 함수는 동일한 결과를 반환합니다.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    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 키워드를 사용함으로써 보다 본문이 긴 것을 볼 수 있습니다.
  • 어떠한 함수든 한 줄로 작성하여 반환되는 코드는 return 키워드를 생략할 수 있습니다.

  • Shorthand Getter Declaration[4]를 보시면, property getter 또한 암시적 반환을 사용하는 걸 볼 수 있습니다.

NOTE 🧑‍💻:

암묵적 반환값으로 쓰이는 코드는 값을 반환해야 합니다. 예를 들어 print(13)을 암묵적 반환값으로 사용할 수 없습니다. 단, Never를 return 하는 함수의 경우 swift는 암묵적 반환이 발생하지 않음을 알고 있기 때문에 fatalError(“Oh no!”)와 같이 암묵적으로 반환할 수 있습니다.

Reference

  1. The Basics — The Swift Programming Language (Swift 5.6)
  2. Camel Case - 위키피디아
  3. 옵셔널(Optional)
  4. Shorthand Getter Declaration