소피it블로그

[Swift 공식문서] Functions 정리 본문

개발_iOS/스위프트

[Swift 공식문서] Functions 정리

sophie_l 2022. 5. 15. 18:44

https://docs.swift.org/swift-book/LanguageGuide/Functions.html

 

Functions — The Swift Programming Language (Swift 5.6)

Functions Functions are self-contained chunks of code that perform a specific task. You give a function a name that identifies what it does, and this name is used to “call” the function to perform its task when needed. Swift’s unified function syntax

docs.swift.org

함수는 특정 업무를 수행하는 코드들의 집합이다. 함수가 무엇을 하는지 나타내는 이름을 지어주고 그 이름을 사용하여 필요할 때 함수를 호출한다.

스위프트에 있는 모든 함수에는 타입이 존재하는데, 이는 함수의 매개변수(parameter) 타입과 리턴 타입으로 구성된다. 이 함수 타입은 스위프트의 다른 모든 타입처럼 쓸 수 있으며, 덕분에 함수를 다른 함수의 매개변수로 넘기거나 함수의 리턴 값으로 또 다른 함수를 반환할 수 있다. 함수 안에 다른 함수를 포함한 중첩 함수 등도 있다.

 

1. 함수 정의하고 호출하기

 

함수를 정의할 때 함수가 입력값으로 갖는 이름과 타입이 있는 값들을 정의해줄 수 있다. 이를 매개변수(parameter)라고 부른다. 또한, 함수가 작업 수행 끝에서 결과물로써 내놓는 값의 타입 또한 지정해줄 수 있는데, 이는 리턴 타입이라고 한다.

모든 함수는 함수명을 갖는다. 함수명은 보통 함수가 수행하는 태스크를 묘사한다. 함수를 사용하기 위해서는 함수명을 통해 함수를 호출하고, 인수(arguments)라고 부른 입력 값들을 전달한다. 이는 함수의 매개변수와 같은 타입이어야 하며, 같은 순서로 제공되어야 한다.

// 함수 정의
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!"

func 키워드로 정의하고 , 리턴 값은 -> 이후에 타입과 함께 적어준다.

함수의 정의는 함수가 무엇을 하고 어떤 값을 받아야 하며 끝났을 때 무엇을 리턴하는지를 보여준다

 

2. 함수의 매개변수와 리턴 값

스위프트에서 함수의 매개변수와 리턴값은 굉장히 유연하다.

 

(1) 매개변수가 없는 함수: 입력 매개변수를 반드시 정의할 필요는 없다.

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

이렇듯 함수가 매개변수를 받지 않는 경우에도 함수명 뒤에 ()를 적어줘야 한다. 호출시에는 함수명 뒤에 빈 괄호와 함께 호출해주면 된다.

 

(2) 여러 개의 매개변수를 갖는 함수

 

함수는 매개변수 여러 개를 가질 수도 있다. 이는 함수의 괄호 부분에 컴마와 함께 써준다.

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

이는 앞에서 나온 greet(person:) 함수와 다르다는 점을 주의해야 한다. 둘다 greet로 시작하는 이름을 가지지만, greet(person:alreadyGreeted:)는 두 개의 인수를 받는 반면 greet(person:) 함수는 하나의 인수만 갖는다.

 

(3) 리턴값이 없는 함수

 

함수는 반드시 리턴 값을 정의할 필요는 없다. 이하의 함수는 String을 리턴하는 대신 출력해준다.

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

리턴 값이 없는 함수이기 때문에 ->나 리턴 타입을 포함하지 않는다. (엄격히 말하자면 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 doesn't return a value

두 번째 함수는 첫 번째 함수를 호출하지만 리턴 값은 무시한다.

 

(4) 여러 개의 리턴 값을 갖는 함수

 

리턴 값이 여러 개인 경우 하나의 포괄적인 리턴 값으로 반환하기 위해 튜플을 사용해줄 수 있다.

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:) 함수는 두 정수 값을 포함한 튜플 한 개를 리턴한다. 해당 정수 값들은 각각 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"

(5) 옵셔널 튜플 반환 타입

 

함수의 리턴 값이 빈 값을 포함할 수 있는 튜플일 경우 리턴 타입을 옵셔널 튜플로 해줄 수 있다. 이 경우 튜플 전체가 nil 값을 가질 수 있다는 의미이다. (Int, Int)?나 (String, Int, Bool)?처럼 튜플 타입의 닫는 괄호 뒤에 ?를 붙여주면 된다.

위의 minMax(array:) 함수에 빈 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"

(6) Implicit Return

 

함수의 바디 전체가 단 하나의 표현으로만 되어있는 경우, 함수는 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!"

리턴 라인에 단 한 줄이 오는 경우 리턴 키워드를 생략해줄 수 있다.

 

3. 함수의 인수 라벨과 매개변수 이름

 

함수의 매개변수는 인수 라벨(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)

(1) 인수 라벨 구체화하기

인수 라벨은 매개변수 전에 적어주며, 공백으로 구분한다.

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

인수 라벨을 사용함으로써 보다 표현력 있고 문장 같은 느낌으로 함수를 호출할 수 있다. 동시에 함수의 바디는 여전히 가독성 있고 의도가 뚜렷하게 유지된다.

 

(2) 인수 라벨 생략하기

 

매개변수에 인수 라벨을 주고 싶지 않으면 _언더스코어를 대신 써줄 수 있다.

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)

(3) 디폴트 매개변수 값

 

매개변수의 타입 뒤에 값을 지정해줌으로써 매개변수에 디폴트 값을 정의해줄 수 있다. 디폴트값이 정의되면 함수를 호출할 때 해당 매개변수를 생략할 수 있다.

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

디폴트 값이 없는 매개변수는 보통 함수의 의미상 더 중요한 것들이 많다. 디폴트 값이 없는 매개변수를 매개변수 리스트의 앞부분에 위치시키고 디폴트 값이 있는 매개변수는 뒤에 위치시키도록 하라.

 

(4) Variadic Parameters

 

variadic 매개변수는 특정 타입의 값을 0개 이상 받는다. 함수가 호출될 때 입력값의 크기에 변화가 있을 수 있는 경우 variadic 매개변수를 사용한다. variadic parameter는 매개변수의 타입 뒤에 ...을 추가해줌으로써 사용할 수 있다.

variadic parameter로 전달된 값들은 함수의 바디에서 해당 타입의 배열로 처리된다. 예를 들어 numbers라는 이름과 Double...이라는 타입의 variadic parameter는 함수의 바디에서 [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

함수는 여러 개의 variadic parameter를 가질 수 있다. variadic parameter 다음에 오는 매개변수는 반드시 인수 라벨을 가져야 한다. 인수 라벨은 어느 인수가 variadic parameter로 전달되고 어떤 인수가 그 뒤에 오는 매개변수로 전달될지를 확실하게 보여준다.

 

(5) In-Out Parameters

 

함수의 매개변수는 기본적으로 상수이다. 함수의 바디에서 매개변수의 값을 바꾸려고 하면 컴파일 타임 에러가 발생한다. 만약 함수가 매개변수의 값을 변형하게 하고 그 변화가 함수가 끝난 후에도 지속되기를 원한다면 해당 매개변수를 in-out 매개변수로 정의하라.

인아웃 매개변수는 inout키워드를 매개변수 타입 전에 붙여줌으로써 만들 수 있다. 인아웃 매개변수는 함수 안으로in 값을 보내주고, 함수에 의해 값이 변형되며, 원래의 값을 대체하면서 함수 바깥out으로 전해진다.

인아웃 매개변수로는 상수나 리터럴 값이 아닌 변수만을 전달해줄 수 있다. 인수로서 값을 인아웃 매개변수에 전달할 때 해당 값이 함수에 의해 변형될 수 있다는 것을 알리기 위해 변수명 앞에 &를 붙여준다.

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"

4. 함수의 타입

 

모든 함수는 특정한 함수 타입이 있다. 이는 매개변수 타입과 함수의 리턴 타입을 의미한다.

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

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

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

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

 

(1) 함수 타입 사용하기

 

스위프트에서는 함수 타입을 다른 모든 타입처럼 쓸 수 있다. 예를 들면 변수나 상수를 함수 타입으로 지정하고 해당 변수에 적절한 함수를 할당해줄 수 있다.

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

print("Result: \(mathFunction(2, 3))")
// Prints "Result: 5"

// mathFunction 변수에 같은 타입의 다른 함수 할당
mathFunction = multiplyTwoInts
print("Result: \(mathFunction(2, 3))")
// Prints "Result: 6"

스위프트는 함수 타입에 대해서도 다른 타입에서처럼 타입 추론을 수행할 수 있다. 변수나 상수에 함수를 할당할 때 타입 추론을 하게 할 수 있다.

let anotherMathFunction = addTwoInts
// anotherMathFunction is inferred to be of type (Int, Int) -> Int

(2) 매개변수 타입으로써의 함수 타입

 

함수 타입을 다른 함수의 매개변수로 사용할 수 있다.

func printMathResult(_ mathFunction: (Int, Int) -> Int, _ a: Int, _ b: Int) {
    print("Result: \(mathFunction(a, b))")
}
printMathResult(addTwoInts, 3, 5)
// Prints "Result: 8"

위의 printMathResult(_:_:_:)는 3개의 매개변수를 갖는데, 그 중 첫 번째 매개변수는 (Int, Int) -> Int 타입의 mathFunction이라는 매개변수이다. 같은 타입의 함수를 해당 매개변수의 인수로 전달해줄 수 있다.

 

(3) 리턴 타입으로써의 함수 타입

 

함수 타입을 다른 함수의 리턴 타입으로 사용할 수 있다. 리턴 화살표 -> 다음에 함수 타입을 써줌으로써 만들 수 있다.

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

// 리턴 타입이 (Int) -> Int인 함수
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!

5. 중첩 함수

 

지금까지 다룬 함수는 전부 global function 전역함수이다. 즉, 전역적인 범위에서 정의된 함수이다. 다른 함수의 바디 안에 새로운 함수를 정의할 수도 있는데, 이를 nested 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!