Swift - 함수(Function)

함수(Function) 라는건 굳이 설명하지 않아도 다들 알겠지만, 특정 기능을 하는 코드를 특정 방법으로 묶어낸 것이다. 아 무슨 말이 이리 어려워 -_- 그냥 자주 쓸 만한 코드를 특정한 이름으로 분리해 낸 거라고 하는게 쉬우려나.

간단히 함수와 관련된 용어 몇 가지만 짚고 시작한다:
  • 인자(Argument): 함수를 호출 할 때 넘겨주는 변수(상수) 혹은 그 이름
  • 매개변수(Parameter): 함수가 정의될 때 함수가 전달받게 되는 변수(상수) 혹은 그 이름
같은 것 같지만 개념 상 의미가 다름에 주의하자. 함수에 전달하느냐 함수가 받느냐 그 차이다.

함수(Function) 정의

함수는 func라는 키워드를 통해 선언이 가능하다. 대충 아래와 같은 형식으로 정의한다.
func FUNCTION_NAME(PARAMETER_NAME: PARAMETER_TYPE, ...) -> RETURN_TYPE {
    FUNCTION_IMPLEMENTATIONS
    return RETURN_VALUE
}
이렇게 적어놓으면 못 알아보니 그냥 예제 몇 가지를 적어보자. 아래 함수는 이름을 넣으면 "Your name is 이름" 이라는 문자열을 리턴하는 함수다.
func getDecoratedName(name: String) -> String {
    return "Your name is \(name)"
}

var str = getDecoratedName("seorenn")
// str = "Your name is seorenn"
리턴 타입이 함수 선언 오른쪽으로 온다는 것을 잘 보자. 리턴값은 항상 -> 라는 표기 이후에 따라오기 때문에 구분하는데는 어렵지 않을 것이다. 개인적으론 이렇게 비상식적(?)으로 우측에 반환 타입이 오는게 좀 마음에 들지 않기도 하지만, 타입을 강제하기 위하면서도 스크립트 언어스러운 문법을 유지하기 위한 어쩔 수 없는 선택 같아서 안스럽기도 하다 -_-;;

매개변수 선언이 Objective-C 혹은 C 와는 다르게 변수 이름이 먼저 오고 그 다음에 타입이 온다. 이는 스위프트의 변수나 상수 선언과 비슷한 문법이니 역시 헷갈릴 것은 없을 것이다.

어떤 언어든 그렇지만 매개변수를 두 개 이상 선언하는 것도 당연히 가능하다.
func sum(a: Int, b: Int) -> Int {
    return a + b
}
var result = sum(1, 2)
매개변수가 필요 없으면 생략 할 수 있다.
func fivePlusSix() -> Int {
    return 5 + 6
}
var result = fivePlusSix()
거기다 리턴값도 없을 경우 리턴타입 생략이 가능하다.
func printSomeLog() {
    println("log A")
    println("log B")
}
printSomeLog()
여기까지는 일반적인 언어들의 함수 문법과 비슷하다고 느껴진다.

리턴 타입으로 튜플의 활용

리턴 타입으로 튜플을 사용해 함수가 멀티 리턴값을 가지게 할 수 있다.
func plusAndMinus(a: Int, b: Int) -> (Int, Int) {
    return (a + b, a - b)
}
물론 튜플에 이름을 붙이는 것도 그대로 쓸 수 있다.
func plusAndMinus(a: Int, b: Int) -> (plus: Int, minus: Int) {
    return (a + b, a - b)
}
튜플에 대해서는 아래 링크를 참고하자.

[관련글] Swift - 튜플(Tuple)

매개변수 별명 붙이기(External Parameter Name)

함수를 호출 할 때 특정 매개변수(Parameter)에 이름(별명)을 붙임으로써 좀 더 읽기 쉬운 코드를 만들 수도 있다. 호출하는 쪽에서도 이름이 붙음으로써 가독성이 높아질 수 있다. 그냥 매개변수 이름 앞에다 쓰고싶은 이름을 붙이면 된다.
func sumAandB(valueA a: Int, valueB b: Int) -> Int {
    return a + b
}

var result = sumAandB(valueA: 10, valueB: 20)
이건 마치 Objective-C의 메소드(셀렉터) 정의 및 호출과 비슷한 것 같다. 물론 그래서 약간의 타이핑 귀찮음을 유발하기도 한다. 필요할 때만 잘 쓰면 될 것 같다.

근데 이보다 약간 더 편하게 축약해서 선언하는 방법이 있다.
func sumAandB(#a: Int, #b: Int) -> Int {
    return a + b
}

var result = sumAandB(a: 10, b: 20)
함수 선언 시 매개변수 이름 앞에 샵(#)을 붙이면 그 이름 그대로 호출 시 인자의 별명으로 쓸 수 있게 된다.

매개변수 기본 값(Parameter Default Value)

특정 매개변수에는 기본값을 넣어서 필요하지 않을 경우 함수 호출을 단순화 시킬 수 있는 방법이 스위프트에서도 제공된다.
func sumOrInc(value: Int, incValue: Int = 1) -> Int {
    return value + incValue
}

var result = 0;
result = sumOrInc(result)                // result = 1
result = sumOrInc(result, incValue: 5)   // result = 6
이 예에서는 incValue 라는 매개변수가 기본값 1 을 가지도록 설계되었다.

다만 타 언어와의 차이점으로, 기본값이 배정된 매개변수에 기본값과 다른 값(인자)을 넣어서 호출하려면 반드시 매개변수 이름을 별도로 적어줘야 한다. 위 예제의 경우 마지막 라인에 해당한다. 참고로 이 이름은 매개변수에 붙이는 별명(External Parameter Name)과는 다르다는 것에 주의하자.

동적인 매개변수(Variadic Parameters)

C의 printf 함수라던가 Objective-C의 NSLog 혹은 NSString의 stringWithFormat 메소드 등의 경우는 매개변수(Parameter)의 갯수가 유동적이다. 이런 경우 처럼 매개변수나 인자가 동적일 경우 함수를 다음과 같은 식으로 선언해서 쓸 수 있다.
func printManyStrings(strs: String ...) {
    for str in strs {
        println(str)
    }
}

printManyStrings("A", "B", "C")
printManyStrings("A", "B", "C", "D", "E", "F")
인자 타입 뒤에 '...' 을 붙이면 자동으로 가변 매개변수 리스트가 완성된다. 그냥 Array 이터레이션 하듯이 쓰면 된다. 호출하는 쪽에서는 마음껏 (타입에 맞게) 인자를 집어 넣을 수 있다.

다만 모든 언어의 동적 매개변수의 특성과 비슷하게, 동적 매개변수는 매개변수 정의에서 가장 마지막 위치에만 정의가 가능하다는 점에 주의하자.

함수의 매개변수는 상수형

아래 예제를 보면 타 언어와는 다른 점이 보인다.
func someFunc(a: Int, b: Int) {
    a++      // ERROR
    b++      // ERROR
}
위 함수 안에 구현된 두 줄의 코드는 모두 오류가 발생한다. 스위프트의 함수 매개변수는 기본적으로 상수(Constant)로 넘어오기 때문에 직접 값을 바꾸는 것이 불가능하다. 호출하는 쪽에서 인자를 상수가 아닌 변수로 넣는다 해도 그 값이 복사되어서 상수로 전달되기 때문에 똑같다.

하지만 값을 바꾸게 할 수 있는 방법도 있다. 매개변수 이름 앞에 var 를 붙이면 된다.
func someFunc(var a: Int, var b: Int) {
    a++
    b++
}
물론 이 값을 바꾸더라도 호출한 쪽에는 아무런 영향이 없다. 함수가 호출 될 때 매개변수로 넘어가는 값은 인자의 값에서 복사되어서 넘어가기 때문이다.

[관련글] Swift - 변수와 상수 그리고 타입

레퍼런스 매개변수?

일반적으로 함수를 호출할 때 전달하는 인자는 함수 실행 후 바뀌는 경우가 없어야 한다. 하지만 필요에 의해 인자로 넘기는 변수의 값이 함수에 의해서 바뀌어야 하는 경우가 있다. 스위프트도 이런 경우를 위해 inout 이라는 기능을 만들어 놨다. 역시 매개변수 이름 앞에 선언하면 된다.
func incValue(inout value: Int) {
    value++
}

var v = 0
incValue(&v)      // v == 1
incValue(&v)      // v == 2
incValue(&v)      // v == 3
자바나 C에서 기본 타입변수의 포인터를 넘길 때와 비슷하게, inout용 인자를 전달 할 때는 앞에 앰퍼선드(&)를 붙여서 호출해야 한다. 이러면 해당 변수가 복사가 아닌 변수의 레퍼런스가 바로 전달되어서 함수가 실행된다. 그리고 보다싶이 inout으로 선언된 매개변수는 상수가 아닌 변수로 전달된다.

아마도 대표적인 레퍼런스 매개변수 기능을 소개하는 함수는 swap이 있겠다. 하지만 여기서는 생략. 너무 유명하니깐 -_-

함수 속의 함수(Nested Function)

함수 내부에서 함수를 정의하는 것 또한 가능하다.
func someFunc() {
    func incAandB(a: Int, b: Int) -> Int {
        return a + b
    }
    println(incAandB(0, 0))    // 콘솔에 0이 찍힌다
    println(incAandB(1, 1))    // 콘솔에 2가 찍힌다
}
someFunc()
함수 속에서 함수 정의하는거야 뭐 굳이 어려울 것은 없다고 생각된다. 하지만 이 부분이 클로져로 확대되면 좀 난해해 질 수도 있다. 자세한 것은 클로져를 참고하자.

[관련글] Swift - 클로져(Closures)

기본적인 함수와 관련된 내용은 여기까지면 충분 할 듯 하다.

[관련글] Swift - @noescape 너 정체가 뭐냐
[돌아가기] 스위프트(Swift) 가이드

댓글

이 블로그의 인기 게시물

소수점 제거 함수 삼총사 ceil(), floor(), round()

버전(Version)을 제대로 이해하기