[Swift] Functions (함수)

LEEHAKJIN-VV·2022년 5월 8일
0

Study-Swift 5.6

목록 보기
7/22

참고사이트
The Swift Programming Language


⚽️ Functions (함수)

🏀 Functions 정의 & 호출

function을 선언할 때 function의 이름 앞에 func 키워드를 붙이고 전달인자 이름과 타입 유형을 function 뒤에 다음과 같이 작성한다. (person:String) 반환값의 타입은 전달인자 뒤에 (->) 기호를 사용하고 반환형의 타입을 명시한다. 반환값은 (Return) 키워드를 함수 제일 마지막에 작성한다. 함수 정의는 다음 예시에서 확인할 수 있다.

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

정의된 function에 파라미터(인자)값을 넣어 호출한 예제.

print(greet(person: "Torr"))
// Prints Hello, Torr!
print(greet(person: "Steven"))
// Prints Hello, Steven!

위 함수에서 인사 메시지를 작성하는 코드와 반환하는 코드를 합쳐 다음과 같이 더 짧게 작성할 수 있다.

func greetAgain(person: String) -> String {
    return "Hello again, " + person + "!"
}

print(greetAgain(person: "Iron Man"))
// Prints Hello again, Iron Man!

🏈 Function 파라미터와 반환 값

⚾️ 파라미터가 없는 Functions

functions은 항상 입력 파라미터를 요구하지 않는다. 여기 입력 파라미터가 없는 function 예제를 살펴보자.

func sayHelloWorld() -> String {
    return "hello world"
}

print(sayHelloWorld())
// Prints "hello world"

함수 입력 파라미터가 없어도 괄호는 무조건 사용해야 한다.


⚾️ 복수의 파라미터를 사용하는 functions

func greet(person: String, alreadyGreet: Bool) -> String{
    if alreadyGreet{
        return greetAgain(person: person)
    } else{
        return greet(person: person)
    }
}

print(greet(person: "Hawkeye", alreadyGreet: true))
// Prints "Hello again, Hawkeye!"

파라미터가 2개 이상일 때, 파라미터 사이에 콤마(,)를 사용하여 구분한다. 이 예제에 나오는 greet이름을 가진 function는 이전 예제에서 파라미터가 1개인 function과 다르다.(이름은 같으나 파라미터의 개수가 다르므로 서로 다른 function)


⚾️ 반환 값이 없는 functions

function은 반환 값의 타입을 명시하지 않아도 된다. 여기는 이전 예제에서 나온 greet(person:) function을 String 값을 반환하지 않고 출력을 하는 예제이다.

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

NOTE
엄밀히 말하자면, 위 예제의 greet(person:) function은 반환 값을 선언하지 않았지만 반환 값이 있다. 반환 값이 정의되어 있지 않는 function은 사실 Void라는 특별한 형을 반환한다. Void는 간단히 ()를 사용한 빈 튜플이다.

print(type(of: greet(person: "Dave")))
// Prints "()"

실제로 greet function 반환 값의 type을 확인해 보면 "()"빈 튜플이 출력되는것을 확인할 수 있다.


🥎 복수의 값을 반환하는 Functions

튜플을 사용하여 2개 이상의 반환 값을 반환할 수 있다.
다음 예제는 Int형 배열에서 최솟값과 최댓값을 찾는 function이다.

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은 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

minMax function의 반환값 타입 부분에서 반환되는 튜플 멤버 이름을 지정했기 때문에 이 이름을 이용하여 반환값에 접근하는 접근자로 사용한다.


🎾 Optional Tuple Return Types

만약 튜플이 값이 없는 빈 튜플로 반환될 수 있다. 이때 이것이 nil인 것을 반영하여 반환 타입을 optional tuple로 지정한다. 방법은 반환형 타입의 마지막에 물음표(?)을 뒤에 추가한다. ex) [(Int, Int)? or (String, Int, Bool)?]

NOTE
(Int, Int)?(Int?, Int?)는 서로 다르다. (Int, Int)?을 사용하면 튜플내의 각각의 값이 optional인 것뿐만 아니라 튜플전체가 optional인것도 포함한다.

그러나 위의 다중 값을 반환하는 튜플 함수에서 만약 입력 파라미터로 빈 Array가 입력된다면 array[0]에서 빈 배열의 인덱스를 접근함으로 runtime error가 발생하게 된다. 이 에러를 안전하게 다루기 위해서 optional tuple이 반환 타입이라고 명시한다. 또한 function의 첫 줄에 빈 배열일 시 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)
}

실제로 반환값에 접근하기 위해서는 if let과 같은 Optional Chaining을 사용하거나 강제 unwrapping을 해야한다.

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 Implict Return

만약 function의 몸체부분이 한줄로 표현된다면 반환을 암시적으로 표현할 수 있다. 아래 예제에서 같은 행동을 하는 function 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!"

greeting function은 return 키워드를 생략하고 더 짧은 코드로 표현한 것을 확인할 수 있다. 그러므로 함수 본체가 한줄이고, 반환 값이 있을 시 reutnr 키워드를 생략할지, 포함할 지 작성자의 선택에 달려있다.


🏐 Function Argument Labels and Parameter Names(함수 인자 라벨과 파라미터 이름)

함수 호출시 적절한 파라미터 이름을 지정해 함수 구현, 호출 시 사용할 수 있다.

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

인자 라벨을 지정하여 함수 내부에서는 hometown으로 값을 제어하고 함수 호출시에는 인자 값으로 from을 사용한 예제.

func greet(person: String, from hometown:String) -> String {
    return "Hello \(person)! Glad you could visit from \(hometown)."
}

print(greet(person: "Bill", from: "Brooklyn"))
// Prints Hello Bill! Glad you could visit from Brooklyn.

🏐 Omtting 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)
// someFunction(1,2) -> error

파라미터가 인자 라벨을 가지고 있다면, 함수를 호출할 때 무조건 라벨된 인자(secondParameterName)를 사용해야 한다.


🏉 Default Parameter Values

파라미터의 타입 뒤에 파라미터에 값을 할당함으로써 파라미터의 default value를 정의할 수 있다. default가 정의된다면, 함수를 호출할 때 그 파라미터를 생략할 수 있다.

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

default value를 사용하지 않는 파라미터는 대게 함수의 의미에 더 중요하기 때문에 앞에 위치시킨다. 그러면 함수를 호출할 때 파라미터를 생략하여 호출한 코드와 생략하지 않은 코드를 더 쉽게 인식할 수 있다.


🎱 Variadic Parameters(가변 파라미터)

variadic parameter는 지정된 타입의 0개 이상의 값을 가질 수 있다. variadic parameter를 사용하여 함수가 호출될 때 파라미터가 다양한 수의 입력값을 전달할 수 있도록 지정할 수 있다. 사용 방법은 세 개의 점(...)을 파라미터의 타입 뒤에 작성한다.

variadic parameter에 전달된 값들은 함수의 몸체 내에서 적절한 타입의 배열로 사용이 가능하다. 예를 들어 이름이 numbers이고 타입이 Double...이 variadic parameter는 함수 내에서 [Double] 타입의 배열로서 사용 가능하다.

다음은 산술 평균을 계산하는 예제.

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

NOTE
함수는 여러 개의 가변 매개변수(variadic parameter)를 가질 수 있다. 가변 매개 변수 다음에 오는 첫 번째 파라미터에는 인수 레이블이 있어야 한다. 인수 레이블은 가변 매개변수에 전달되는 인수와 가변 매개변수 다음에 오는 파라미터에 전달되는 인수를 명확하게 한다.

다음 예제를 살펴보면 가변 매개변수 뒤에 파라미터에 대해 인수 레이블을 지정하지 않으면 가변 매개변수로 전달되는 값이 어디까지 인지 명확하게 구분이 안되기 때문에 에러가 난다. 이를 예방하기 위해 인자 레이블을 지정하였다.

func arithmeticMean(_ numbers: Double..., length len: Int) -> Double {
    var total: Double = 0
    for number in numbers {
        total += number
    }
    return total / Double(len)
}
arithmeticMean(1, 2, 3, 4, 5, length: 5)
// returns 3.0, which is the arithmetic mean of these five numbers
arithmeticMean(3, 8.25, 18.75, length: 3)
// returns 10.0, which is the arithmetic mean of these three numbers

🪀 In-Out Parameters

함수 파라미터는 기본적으로 상수다. 함수 몸체 내부에 있는 함수 파라미터의 값을 변경하려고 시도하면 compile-time error가 발생하는 것을 확인할 수 있다. 이는 실수로 파라미터의 값을 변경할 수 없는 것을 의미한다. 만약 함수의 값을 수정하기를 원하고, 함수의 호출이 종료된 후에도 변화가 지속되기를 원한다면 일반 파라미터 대신에 in-out parameter를 사용한다.

in-out parameter를 정의하려면 파라미터의 타입 앞에 inout 키워드를 작성한다. in-out parameter는 함수로 전달되는 값을 가지고 있고 함수에 의해서 수정되며, 수정된 값을 원래의 값 대신에 함수의 밖으로 전달된다.

in-out parameter에 대한 인수로는 변수(var)만 가능하다. constant, literals는 수정될 수 없기 때문이다. in-out parameter에 인수로 전달할 때 변수의 이름 뒤에 앰퍼샌드(&)를 작성한다.

NOTE
in-out parametersms default values를 가질수 없고, 가변 파라미터(variadic parameters)는 inout으로 선언될 수 없다.

여기에 swapTwoInts(:,:) 함수가 있고, 2개의 in-out parameters를 가진다.

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

swapTwoInts(:,:)함수는 파라미터의 입력으로 들어온 a, b 두 값을 변경하는 함수다.

아래는 실제로 사용하는 예제다. 함수를 호출할 때 인자의 앞에 (&) 키워드를 넣는 것을 까먹지 말자. c언어를 사용해 본 적이 있다면 in-out parameter에 포인터를 넣는다고 생각하자.

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
in-out parameter는 함수에서 값을 반환하는 것이랑 다르다.(swapTwoInts 예제는 반환 타입과 반환 값을 정의하지 않음) 그리고 in-out parameter는 함수의 반환 값을 사용하지 않고 함수 scope 밖에 영향을 줄 수 있는 또 다른 방법이다.


🏓 Function Types(함수 형)

모든 함수의 형은 파라미터 형과 반환 형으로 구성되어 있다. 아래 두 함수는 Int값 2개를 입력받고 Int를 반환하는 함수다.

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

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

아래는 입력 받는 파라미터와 반환 값이 없는 함수이다. (실제로 반환 값이 없어보이지만 Void라는 특별한 형을 반환한다.)

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

🏐 Using Function Types(함수 형의 사용)

다른 언어와 같이 Swift에서도 함수 타입을 다른 타입처럼 사용할 수 있다.(함수를 상수나 변수처럼 정의해서 사용) 예를 들어 상수나 변수를 함수 타입으로 정의하고 변수에 적절한 타입을 할당할 수 있습니다.

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

변수 mathFunctionaddTwoInts함수의 타입과 같기 떄문에 이 함수가 변수로 할당될 수 있다. 아래는 이렇게 변수에 함수를 할당한 예제입니다.

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(파라미터 형으로써의 함수 형)

다음과 같이 파라미터에 함수 형을 사용할 수 있다.

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

🏈 Function Types as Return Types (반환 형으로써의 함수 형)

함수의 반환 타입을 함수 타입으로 지정할 수 있다. 반환하는 함수의(->) 바로 뒤에 완전한 함수 유형을 작성하여 이를 작성할 수 있다.

다음 예제는 입력값을 1 더하거나 빼는 2개의 함수를 정의했다. 두 함수 모두 (Int) -> Int 유형을 가진다.

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

여기에 반환 타입이 (Int) -> IntchooseStepFunction 함수가 있다. 이 함수는 bool 값에 따라 적절한 함수를 반환한다.

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

위 예제에서는 bool 값이 true이므로 stepBackward함수가 반환된다. 이 함수에 대한 참조는 상수 moveNearerToZero에 있다. 즉 이 상수를 호출한 다는 것은 stepBackward함수를 호출하는 것이다. 다음 예제를 통해 확인해 보자.

print("Counting to zero:")
// Counting to zero:
while currentValue != 0 {
    print("\(currentValue)... ")
    currentValue = moveNearerToZero(currentValue)
}
print("zero!")
// 3...
// 2...
// 1...
// zero!

🎾 Nested Functions (중첩 함수)

이때까지 본 함수들은 global scope(전역 범위)에서 동작하는 global function(전역 함수)이다. 그러나 nested function(중첩 함수)라고 하는 함수는 다른 함수의 본문 내에 정의할 수 있다.

중첩 함수는 함수 body 밖에서는 숨겨져 있지만 중첩 함수를 가지는 함수에 의해 호출되고 이 함수는 다른 범위에서 중첩 함수를 반환한다.

위의 chooseStepFunction을 중첩 함수를 이용해 아래처럼 다시 작성할 수 있다.

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개의 댓글