References
Contents
- Defining and Calling Functions
- Function Parameters and Return Values
- Function Argument Labels and Parameter Name
- Function Types
- Nested Functions
Functions(함수)는 특정 작업을 수행하는 코드 덩어리입니다. 함수가 수행하는 작업을 식별하는 이름을 지정하고, 해당 작업을 수행할 때 함수 이름을 호출(call)하여 사용합니다.
Swift의 통합 함수 문법은 유연해서 매개변수 이름이 없는 단순한 C 스타일의 함수부터 Objective-C 스타일 메소드까지 모든 것들을 표현할 수 있습니다. 매개변수는 함수 호출을 단순화하기 위해서 기본값을 제공하고 있고, in-out 파라미터로 전달될 수도 있어서 함수 실행이 끝나면 전달된 변수를 수정할 수도 있습니다.
그럼 Swift의 함수에 대해서 알아보도록 하겠습니다.
Defining and Calling Functions
함수를 정의할 때, 선택적으로 입력으로 받을 하나 이상의 파라미터를 정의할 수 있습니다. 또한, 함수로부터 반환받을 return type을 선택적으로 정의할 수 있습니다.
모든 함수는 function name(함수 이름)을 가집니다. 함수를 사용하기 위해서는 함수 이름을 호출(call)하는데, 입력 값(argument)들도 함께 전달해줍니다. 함수 argument는 항상 함수의 파라미터 리스트와 동일한 순서로 전달되어야 합니다.
아래 예제코드는 greet(person:)으로 호출되는 함수를 보여주고 있습니다. 이 함수는 하나의 String 타입의 person이라는 input 파라미터가 정의되어 있고, String 타입의 return type이 정의되어 있습니다.
func greet(person: String) -> String {
let greeting = "Hello, " + person + "!"
return greeting
}
함수를 선언할 때는 func 키워드를 붙이고 그 뒤에 함수의 이름과 파라미터, 리턴타입을 명시합니다.
파라미터의 형태는 'person: String'이고, 리턴 타입은 return arrow(->)뒤에 타입을 명시해주는 형태로 작성됩니다.
print(greet(person: "Anna"))
// Prints "Hello, Anna!"
print(greet(person: "Brian"))
// Prints "Hello, Brian!"
greet(person:) 함수는 person이라는 argument 라벨 뒤에 String 값을 전달하여 호출할 수 있습니다. 이 함수는 String 값을 반환하기 때문에 greet(person:) 함수는 print(_:separator:terminator:) 함수 안에서 사용될 수 있고, 반환된 값을 출력합니다.
greeting 함수 내부에서 greeting이라는 새로운 String 상수가 정의되고, 간단한 메세지로 설정됩니다. 그리고 이 값은 함수로부터 반환(return)되는데, return 키워드를 사용합니다. 함수 내부에서 'return greeting'을 만나면 함수는 함수 실행을 끝내고, greeting의 현재 값을 반환합니다.
함수 내에서 return 문과 함께 한 줄에 메세지를 작성할 수도 있습니다.
func greetAgain(person: String) -> String {
return "Hello again, " + person + "!"
}
print(greetAgain(person: "Anna"))
// Prints "Hello again, Anna!"
Function Parameters and Return Values
Functions without Parameters
함수에서 입력 파라미터를 반드시 정의할 필요는 없습니다. 아래 예제 코드의 함수는 입력 파라미터가 없고, 항상 동일한 String 메세지를 반환합니다.
func sayHelloWorld() -> String {
return "hello, world"
}
print(sayHelloWorld())
// Prints "hello, world"
파라미터는 없지만 함수를 호출할 때 함수 이름 뒤의 괄호()는 꼭 붙여주어야 합니다. 입력 파라미터가 없기 때문에 괄호 내의 전달되는 값들은 없습니다.
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!"
greet(person:alreadyGreeted:) 함수는 person으로 라벨된 String 인수(argument)와 alreadyGreeted라고 라벨된 Bool 인수를 전달받아서 호출됩니다. 이 함수는 위에서 살펴본 greet(person:) 함수와 구분되는데, 두 함수가 greet라는 같은 이름의 함수이지만, 전달받은 인수가 다르므로, 다른 함수로 취급합니다.
Functions Without Return Values
함수에서 return type을 꼭 정의할 필요가 없습니다. 새로운 버전의 greet(person:) 함수가 아래 정의되어 있습니다. 이 함수는 메세지를 리턴하지 않고, 내부에서 자신만의 String 값을 출력합니다.
func greet(person: String) {
print("Hello, \(person)!")
}
greet(person: "Dave")
// Prints "Hello, Dave!"
이 함수는 리턴값 필요없기 때문에 함수를 정의할 때 return arrow(->)와 return type을 포함할 필요가 없습니다.
그리고 위에서 리턴값이 존재하는 greet(person:) 함수와 이 함수는 서로 구분되지 않습니다.
따라서, 두 함수가 같이 정의되었다면 컴파일 에러가 발생합니다.
Functions with Multiple Return Values
튜플 형식을 함수의 리턴 타입으로 사용하여 여러 값을 묶어서 하나의 튜플로 리턴할 수 있습니다.
아래 코드는 minMax(array:) 함수를 정의하고 있으며, 이 함수는 Int 타입의 배열에서 가장 작은 수와 큰 수를 찾아서 튜플값으로 리턴합니다.
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"
반환되는 튜플 멤버의 이름은 함수 리턴타입을 정의할 때 이미 명명되어 있습니다.
Optional Tuple Return Types
만약 함수로부터 반환되는 튜플 타입이 잠재적으로 'no value'를 가진다면, optional 튜플 타입을 사용하여 튜플이 nil이 될 수 있음을 반영할 수 있습니다. 이는 리턴 타입을 (Int, Int)?나 (String, Int, Bool)?과 같이 튜플 리턴 타입에 물음표 마크를 붙여서 Optional 튜플 리턴 타입으로 작성하면 됩니다.
위에서 살펴본 minMax 함수는 두 개의 Int 값을 튜플로 리턴하는데, 입력으로 전달받는 배열의 안전성 검사가 수행되지 않고 있습니다. 만약 array 인수가 빈 배열이라면 위에서 정의한 minMax 함수는 런타임 에러가 발생합니다.
빈 배열도 안전하게 처리하기 위해서 아래 minMax 함수는 optional 튜플 리턴 타입을 사용하여, 입력으로 빈 배열이 전달되면 nil을 반환하도록 합니다. optional 타입을 반환하기 때문에 사용하려면 optional binding을 사용하면 됩니다.
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"
Functions with an Implicit Return
함수 body가 하나의 표현식으로만 작성되어 있다면, 함수는 암시적으로 그 표현식을 리턴합니다. 예를 들어, 아래 두 함수는 동일한 동작을 보여줍니다.
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 함수의 경우 return을 생략해도, 알아서 함수 내 표현식을 리턴해주고 있습니다.
Function Argument Labels and Parameter Names
각 함수의 파라미터는 argument label과 parameter name을 가집니다. argument label은 함수를 호출할 때 사용되고, 함수를 호출할 때 각 인수 앞에 argument label이 포함되어야 합니다.
parameter name은 함수의 실행에서 사용됩니다. 기본적으로 parameter name은 argument label로 사용할 수 있습니다.
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)
위 코드에서 firstParameterName과 secondParameterName은 parameter name이자 argument label이 됩니다.
Specifying Argument Labels
코드를 더 읽기 쉽도록 하기 위해서 parameter name 앞에 argument label을 추가할 수 있고, 공백으로 구분합니다.
func someFunction(argumentLabel parameterName: Int) {
// In the function body, parameterName refers to the argument value
// for that parameter.
}
someFunction에서 argumentLabel은 함수를 호출할 때 사용되고, parameterName은 함수 내부 실행에서 사용됩니다.
아래의 새로운 greet 함수를 살펴보겠습니다.
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."
사람의 이름과 고향을 입력으로 전달받아서 greeting을 리턴하는 greet 함수입니다. 두 번째 파라미터는 'from hometown: String'으로 정의되는데, 함수 호출에서는 from을 사용하고 함수 내부에서는 hometown을 사용하고 있는 것을 확인할 수 있습니다.
Omitting Argument Labels
만약 파라미터에서 argument label을 원하지 않는다면 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)
파라미터가 argument label을 가지고 있다면, 함수를 호출할 때 반드시 argument가 라벨되어야 하지만 '_'를 사용하면 위 코드처럼 단순히 값만 써주면 됩니다.
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
기본값이 없는 파라미터를 함수 파라미터 리스트의 앞쪽에 위치시켜 기본값이 있는 매개변수 앞에 오도록 하는 것이 가독성이 더 좋습니다만, C++과는 위치로 인해 컴파일 에러는 발생하지 않습니다.
func someFunction(parameterWithDefault: Int = 12, parameterWithoutDefault: Int) {
// If you omit the second argument when calling this function, then
// the value of parameterWithDefault is 12 inside the function body.
}
someFunction(parameterWithoutDefault: 4) // parameterWithDefault is 12, no error
Variadic Parameters
가변 파라미터(variadic parameter)는 지정된 타입의 0개 이상의 값들을 전달받습니다. 가변 파라미터를 사용하면 함수를 호출할 때 다양한 수의 입력 값을 해당 파라미터로 전달할 수 있습니다. 가변 파라미터는 파라미터 타입 뒤에 '...'을 붙여주어서 사용할 수 있습니다.
가변 파라미터로 전달된 값들은 함수 body 내에서 적절한 타입의 배열로 사용할 수 있습니다. 예를 들어, 아래 코드에서 numbers라는 이름의 Doube... 타입의 가변 파라미터가 정의되어 있는데, 이 numbers는 함수 body에서 [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
함수는 여러 개의 가변 파라미터를 가질 수 있습니다. 다만, 두 번째 가변 파라미터부터는 argument label을 필수로 정의해야하는데, 정의되어 있지 않다면 어디까지가 첫 번째 가변 파라미터인지 모호하기 때문입니다.
In-Out Parameters
함수의 파라미터는 기본적으로 상수입니다. 함수 내에서 파라미터의 값을 변경하려고 하면 컴파일 에러가 발생합니다. 이는 실수로 파라미터의 값을 변경하지 못하도록 합니다. 만약 함수 내에서 파라미터의 값을 변경하고 함수 호출이 끝난 후에 이 변경사항이 유지되도록 하고 싶다면 해당 매개변수를 in-out 파라미터로 정의하면 됩니다.
in-out 파라미터는 파라미터의 타입 앞에 inout 키워드를 배치하여 사용할 수 있습니다. in-out 매개변수는 함수로 전달된 값을 가지고, 함수 내에서 수정된 후에 변경된 값이 원본값을 대체하기 위해서 함수 밖으로 다시 전달됩니다.
in-out 파라미터 동작와 컴파일러 최적화와 관련된 자세한 내용은 link를 참조하시길 바랍니다.
in-out 파라미터에는 오직 변수만을 인수로 전달할 수 있고, 상수나 리터럴 값은 전달할 수 없습니다. 변수를 함수의 in-out 파라미터로 전달할 때에는 변수 이름 앞에 ampersand(&)를 붙입니다.
아래는 in-out 파라미터 예제 코드로 swap 함수를 보여주고 있습니다.
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"
Function Types
모든 함수는 파라미터 타입과 리턴 타입으로 구성되는 지정된 function type을 가집니다.
예를 들면,
func addTwoInts(_ a: Int, _ b: Int) -> Int {
return a + b
}
func multiplyTwoInts(_ a: Int, _ b: Int) -> Int {
return a * b
}
위의 간단한 두 함수 addTwoInts와 multiplyTwoInts가 있습니다. 각 함수는 두 개의 Int 값을 받아서 적절한 연산한 값을 리턴하는 함수입니다.
두 함수의 타입은 (Int, Int) -> Int 입니다. 이는 이 함수가 Int 타입인 두 개의 파라미터를 전달받고, Int 타입의 값을 리턴한다는 것을 의미합니다.
다른 예제로 파라미터와 리턴값이 없는 함수의 타입은 () -> Void가 됩니다.
func printHelloWorld() {
print("hello, world")
}
Using Function Types
function type은 swift의 다른 타입들과 동일하게 사용할 수 있습니다. 예를 들어, 함수 타입으로 상수나 변수를 정의하고 적절할 함수를 할당할 수 있습니다.
var mathFunction: (Int, Int) -> Int = addTwoInts
이는 mathFunction이라는 변수를 2개의 Int 값을 매개변수로 받고, Int 값을 리턴하는 함수 타입으로 정의한다는 것을 의미하고, addTwoInts라는 함수를 참조하도록 할당했습니다.
addTwoInt(_:_:) 함수는 mathFuction 변수와 동일한 타입을 가지기 때문에 addTwoInt 함수는 mathFunction으로 할당될 수 있습니다. 이제 mathFunction이라는 이름으로 동일한 함수를 호출할 수 있습니다.
print("Result: \(mathFunction(2, 3))")
// Prints "Result: 5"
같은 타입을 가지는 다른 함수를 mathFunction에 할당할 수 있습니다.
mathFunction = multiplyTwoInts
print("Result: \(mathFunction(2, 3))")
// Prints "Result: 6"
다른 타입과 마찬가지로 함수 타입에도 swift의 타입 추론이 적용됩니다. 따라서 아래의 anotherMathFunction은 (Int, Int) -> Int 타입으로 추론되어 타입이 정의됩니다.
let anotherMathFunction = addTwoInts
// anotherMathFunction is inferred to be of type (Int, Int) -> Int
Function Types as Parameter Types
(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"
이 예제 코드에서 printMathResult 함수는 3개의 파라미터를 전달받습니다. 첫 번째 파라미터는 정의된 타입의 어떤 함수를 전달받고, 두 번째 세번째 파라미터는 Int 타입의 값을 전달받습니다.
Function Types as Return Types
다른 함수의 리턴 타입으로 함수 타입을 사용할 수도 있습니다.
아래 예제 코드는 stepForward와 stepBackward 함수를 구현하고, chooseStepFunction에서 앞으로 한칸 전진할지 뒤로 한칸 전진할지 결정하여 적절한 함수를 리턴하도록 했습니다. 코드 마지막에 moveNearerToZero는 stepBackward 함수를 가리키게 됩니다.
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
moveNearerToZero에 함수를 할당하고, 다음과 같이 사용할 수 있습니다.
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)에서 정의되는 전역 함수입니다.
하지만 다른 함수의 body 내에서 중첨된 함수(nested function)를 정의할 수도 있습니다.
중첩된 함수는 기본적으로 해당 함수가 정의된 함수 외부에서는 숨겨져있지만, 해당 함수에 의해서 호출되고 사용될 수 있습니다. 따라서, 중첩된 함수를 포함하는 함수는 중첩된 함수들 중에 하나를 반환하여 중첩 함수를 다른 scope에서 사용할 수 있도록 할 수 있습니다.
바로 위에서 살펴본 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!
'프로그래밍 > Swift' 카테고리의 다른 글
[Swift] Enumerations (열거형, 열거자) (0) | 2021.12.22 |
---|---|
[Swift] Closures (클로저) (0) | 2021.12.22 |
[Swift] Control Flow (제어문) (0) | 2021.12.16 |
[Swift] Collection Type (Array, Set, Dictionary) (0) | 2021.12.15 |
[Swift] 문자열과 문자 (Strings and Characters) (0) | 2021.12.14 |
댓글