티스토리 뷰

Swift 공식 가이드/Swift 2

Functions

찜토끼 2016. 3. 12. 11:25

Swift 3.0.1 가이드에 대응하는 정리글을 작성하였습니다!!!

Functions 정리 최신버전 링크 > http://wlaxhrl.tistory.com/39




Apple 제공 Swift 프로그래밍 가이드(2.1)의 Functions 부분을 공부하며 정리한 글입니다. 개인적인 생각도 조금 들어가있습니다.



들어가며

함수는 특정 task를 수행하는 자기완결성(self-contained)을 가진 코드들의 집합이다. Swift에서의 함수 문법은 C 스타일의 함수부터 Objective-C 스타일의 메서드까지 모두 표현할 수 있을만큼 유연하다. 파라미터에는 디폴트 값을 설정할 수 있고, 변수를 in-out 파라미터로 넘겨서 함수 실행 후 해당 변수의 값이 변하게 만들 수도 있다.

Swift의 모든 함수는 타입을 가진다. 이 타입이라는 것은 함수의 파라미터 타입과 리턴 타입으로 구성되어 있으며, Swift에서 쓰이는 다른 타입들과 마찬가지로 쓸 수 있다. 따라서 함수의 파라미터로 다른 함수를 넘기거나 함수에서 함수를 리턴하는 것이 용이해진다. 또한 기능 캡슐화를 위해 함수는 다른 함수의 내부에 쓰여질 수도 있다.



Defining and Calling Functions

함수를 정의할 때는, 함수가 input으로 받을 값의 타입과 이름, 즉 parameters를 하나 또는 그 이상으로 정의할 수 있으며, 함수의 실행이 끝났을 때 돌려줄 값의 타입, 즉 return type을 정의할 수 있다.

모든 함수는 이름을 가진다. 함수의 이름은 해당 함수가 수행하는 일을 잘 설명할 수 있어야 한다.

함수는 arguments를 넘기며 함수의 이름을 call 하는 것으로 사용할 수 있다. argument는 함수의 파라미터와 타입이 일치해야 하며 순서도 일치해야 한다.

예제를 보자. sayHello라는 함수이다. 함수의 이름이 함수의 역할을 설명해주고 있다. 이 함수는 personName이라는 이름의 String 타입 파라미터를 가지며, String 리턴 타입을 가진다. 리턴 타입을 정의할 때는 함수 이름 옆에 -> 을 쓴다.

func sayHello(personName: String) -> String {
    let greeting = "Hello, " + personName + "!"
    return greeting
}

print(sayHello("Anna"))
// prints "Hello, Anna!"
print(sayHello("Brian"))
// prints "Hello, Brian!"

함수 내에서 return 을 하면 그 즉시 함수의 실행이 종료되고 리턴값이 반환된다.



Function Parameters and Return Values

Swift에서 함수 파라미터와 리턴값은 극도로 유연하다. 이름없는 파라미터를 사용하는 심플한 기능성 함수에서부터, 명시적인 파라미터 이름을 사용하고 각각 파라미터 옵션이 다른 복잡한 함수까지 전부 정의할 수 있다. 하나씩 살펴보자.


<Functions Without Parameters>

함수에 항상 파라미터를 정의할 필요는 없다. 파라미터가 필요없는 함수의 예제를 보자.

func sayHelloWorld() -> String {
    return "hello, world"
}
print(sayHelloWorld())
// prints "hello, world"
// <참고> 파라미터가 없더라도 함수 call 시에는 괄호를 써줘야 한다


<Functions With Multiple Parameters>

함수에 여러 개의 파라미터를 정의할 수 있다. 다음의 예제를 보자.

func sayHello(personName: String, alreadyGreeted: Bool) -> String {
    if alreadyGreeted {
        return sayHelloAgain(personName)
    } else {
        return sayHello(personName)
    }
}
print(sayHello("Tim", alreadyGreeted: true))
// prints "Hello again, Tim!"
// <참고> 파라미터가 두 개 이상이면 두 번째 argument부터는 매칭되는 파라미터 이름을 명시해주어야 한다
// <참고> 파라미터 이름을 명시하더라도 순서가 바뀌면 컴파일 에러가 난다


<Functions Without Return Values>

함수에 항상 리턴값이 필요한 것은 아니다. 다음의 예제를 보자.

func sayGoodbye(personName: String) {
    print("Goodbye, \(personName)!")
}
sayGoodbye("Dave")
// prints "Goodbye, Dave!"

<note> 리턴값이 정의되지 않은 함수도 사실은 Void 라는 리턴값을 반환한다. Void는 ( ) 라고 쓸 수 있는 empty tuple이다.


<Functions with Multiple Return Values>

 함수는 여러 개의 값을 리턴할 수 있다. 이때 여러 개의 값은 하나로 합성이 되어있어야 한다. 예제를 보자. tuple 타입의 값을 리턴하는 함수이다.

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)
   // 리턴 시점에서 tuple의 요소들의 이름을 지을 필요는 없다.
   // 이미 함수 파라미터에 명시가 되어있기 때문에, min과 max라는 이름이 이미 붙어있다.
}


let bounds = minMax([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>

만약 함수의 반환값이 tuple 타입이고 이 값이 nil일 가능성이 있다면 Optional Tuple 타입을 리턴타입으로 정의해야 한다. (Int, Int)? 혹은 (String, Int, Bool)? 과 같이 뒤에 ? 마크를 붙인다.

<note> (Int, Int)?와 (Int?, Int?)는 다른 의미이다.

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 minMaxTuple = minMax([]) {
    print(minMaxTuple.min)
} else {
    print("Empty!")
}



Function Parameter Names

함수의 파라미터는 external parameter namelocal parameter name 을 둘 다 가진다. external parameter name 은 함수를 호출할 때 arguments에 이름을 지정하기 위해 사용되고, local parameter name 은 함수 내부 구현 안에서 사용이 되는 이름이다.

func someFunction(firstParameterName: Int, secondParameterName: Int) {
    // function body 시작
    // firstParameterName 과 secondParameterName 은
    // 함수로 넘어오는 첫 번째와 두 번째 argument 값을 각각 가리킨다
}
someFunction(1, secondParameterName: 2)

디폴트로 첫 번째 파라미터는 external name이 생략되고, 두번째 파라미터부터는 자신의 local name을 external name으로 사용한다. 모든 파라미터는 반드시 서로 구분되는 local name을 가져야 한다. external name의 경우에는 파라미터 두 개가 동일한 external name을 가져도 컴파일 에러는 나지 않지만, 당연히 서로 구분되게 정의하는 것이 좋다.


<Specifying External Parameter Names>

external parameter name을 정의하는 방법은 local parameter name의 왼쪽에 스페이스바로 구분하여 명시하는 것이다. external parameter name 이 따로 정의되어 있는 파라미터는 함수를 호출할 때 꼭 그 이름을 명시해주어야 한다. 설령 첫 번째 파라미터라도 그렇다.

func someFunction(externalParameterName localParameterName: Int) {
    // 밖에서 함수를 호출할 때는 externalParameterName이라는 이름으로 argument를 넘기고
    // function body 에서는 localParameterName 을 사용하여 구현을 한다
}


func sayHello(to person: String, and anotherPerson: String) -> String {
    return "Hello \(person) and \(anotherPerson)!"
}
print(sayHello(to: "Bill", and: "Ted"))
// prints "Hello Bill and Ted!"

이렇게 함수 밖과 안에서 각각 다른 이름을 지을 수 있게 만든 목적은? external name은 함수를 명시적이고 말이 되게끔(sentence-like) 호출할 수 있게 만들어준다. local name은 함수 내부 구현을 가독성있고 의도한 바를 명확하게 (clear in intent) 보일 수 있게 해준다.


<Omitting External Parameter Names>

함수를 호출할 때 첫 번째 파라미터는 external name을 명시하지 않아도 된다. 그러나 두 번째 파라미터부터는 명시해주어야 한다. 이것이 필요없다면 파라미터에 언더바(_)를 extenal name으로 붙이면 된다.

func someFunction(firstParameterName: Int, _ secondParameterName: Int) {
    // 뭔가 한다
}
someFunction(1, 2)


<Default Parameter Values>

모든 함수 파라미터는 디폴트 값을 설정할 수 있다. 디폴트 값이 있는 파라미터는 함수 호출 시 생략할 수 있다.

func someFunction(parameterWithDefault: Int = 12) {
    // 어떤 arguments 도 넘어오지 않으면 
    // parameterWithDefault 는 12 가 된다
}
someFunction(6) // parameterWithDefault is 6
someFunction() // parameterWithDefault is 12

<note> 디폴트 값을 가진 파라미터들은 파라미터 리스트의 마지막에 두는 게 좋다. 그래야 디폴트 값이 없는 파라미터들이 항상 같은 순서임을 보장할 수 있고, 매번 함수가 호출될 때마다 같은 함수가 호출되는 것이 명백해진다.

(원문 : Place parameters with default values at the end of a function’s parameter list. This ensures that all calls to the function use the same order for their nondefault arguments, and makes it clear that the same function is being called in each case.)


<Variadic Parameters>

variadic parameter 는 가변 개수의 파라미터를 뜻한다. 특정 타입의 값을 0개 이상 받을 수 있다. 파라미터의 타입 이름 옆에 마침표 세 개 (...)를 써서 사용할 수 있다. variadic parameter 는 함수 내부에서 적절한 타입의 배열로 만들어져서 사용된다. 예제를 보자.

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> 함수는 최대 1개의 variadic parameter를 가질 수 있다.


<Constant and Variable Parameters>

함수의 파라미터는 디폴트가 상수이다. 따라서 함수 내부에서 파라미터값을 변경하려고 하면 컴파일 에러가 난다. 그러나 때때로 파라미터의 값을 복사하여 사용하는 것이 유용할 때도 있는데, 그럴때는 variable parameter 를 사용한다. 파라미터 이름 앞에 var 키워드를 붙이면 된다. 그러면 파라미터를 상수가 아닌 변수로 취급하며, 파라미터값의 복사값을 제공한다.

func alignRight(var string: String, totalLength: Int, pad: Character) -> String {
    let amountToPad = totalLength - string.characters.count
    if amountToPad < 1 {
        return string
    }
    let padString = String(pad)
    for _ in 1...amountToPad {
        string = padString + string
    }
    return string
}
let originalString = "hello"
let paddedString = alignRight(originalString, totalLength: 10, pad: "-")
// paddedString is equal to "-----hello"
// originalString is still equal to "hello"

위의 예제와 같이 var 키워드를 사용한 파라미터는 함수 내부에서 값을 수정할 수 있다. 그러나 함수 밖의 원본은 변하지 않는다.


<In-Out Parameters>

variable parameter 는 위에서 설명한 것처럼 함수 내부에서만 값이 바뀌고 원본은 바뀌지 않는다. 만약 함수 내부에서의 변수 값 변화가 원본에도 적용되기를 원한다면 in-out parameter 를 사용해야 한다.

inout 키워드를 붙이면 in-out 파라미터가 된다. 그러면 함수로 넘겨받은 (in) 값이 함수 내부에서 변경되고 이것이 함수 밖으로 나가서 (out) 원본 값을 대체하게 된다. in-out 파라미터의 자세한 원리는 나중에 다룹니다.

in-out 파라미터 argument로 넘길 수 있는 것은 오직 변수 뿐이다. 상수나 문자열 리터럴 등은 나중에 변경할 수 없기 때문이다. in-out 파라미터로 넘길 변수는 & 를 변수 이름에 붙여 함수를 호출하면 된다.

<note> In-out 파라미터는 디폴트 값을 가질 수 없으며 variadic parameters도 될 수 없다. 또한 inout 키워드가 붙은 파라미터는 var나 let을 붙일 수 없다.

func swapTwoInts(inout a: Int, inout _ b: 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을 가진다. fuction type은 함수의 파라미터들 타입과 리턴 타입으로 구성되어 있다.

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

// function type : (Int, Int) -> Int (두 개의 int 타입의 파라미터를 가지고 int 타입의 값을 리턴한다)
func multiplyTwoInts(a: Int, _ b: Int) -> Int {
    return a * b
}

// function type : () -> Void (파라미터가 없고 Void를 리턴한다)
func printHelloWorld() {
    print("hello, world")
}


<Using Function Types>

Swift에서 함수 타입은 다른 타입들이 사용되는 것처럼 똑같이 사용할 수 있다.

// 함수 타입의 상수나 변수를 만들어서 적절한 함수를 할당
var mathFunction: (Int, Int) -> Int = addTwoInts

// 할당한 함수를 호출해서 사용할 수 있다
print("Result: \(mathFunction(2, 3))")
// prints "Result: 5"

// 다른 상수나 변수에 할당하기. 다른 변수와 마찬가지임
mathFunction = multiplyTwoInts
print("Result: \(mathFunction(2, 3))")
// prints "Result: 6"

// 함수 타입을 할당받은 상수/변수의 타입은 컴파일러에 의해 유추된다
let anotherMathFunction = addTwoInts
// anotherMathFunction 은 (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"
// printMathResult 함수는 mathFunction의 내부구현이 무엇인지 몰라도 되고, 단지 맞는 함수 타입인지 체크만 한다


//  부가설명
func swapTwoInts(inout a:Int, inout _ b:Int) {
    let temp = a
    a = b
    b = temp
}

func mathFuntion(mainFunction: (inout Int, inout Int) -> (), inout _ a: Int, inout _ b: Int) {
    mainFunction(&a, &b)
}

var a = 4
var b = 5
mathFuntion(swapTwoInts, &a, &b)


<Function Types as Return Types>

함수 타입을 다른 함수의 리턴 타입으로 사용할 수도 있다. 함수의 파라미터 정의 옆의 -> 옆에 바로 함수 타입을 쓰면 된다.

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

func chooseStepFunction(backwards: Bool) -> (Int) -> Int {
    return backwards ? stepBackward : stepForward
}

var currentValue = 3
let moveNearerToZero = chooseStepFunction(currentValue > 0)
// moveNearerToZero : stepBackward() function



Nested Functions

지금까지 예제로 본 함수들은 전부 global scope 를 가지는 global 함수들이었다. 함수의 정의가 global 영역에 되었기 때문이다. 이와 다르게 함수 안에 다른 함수를 정의할 수도 있는데, 이런 함수를 nested function 이라고 부른다. nested function은 scope가 그 함수를 감싸고 있는 함수(enclosing function)이며, 그 바깥에서는 보이지 않도록 감추어져 있는 것이 기본이다.

func chooseStepFunction(backwards: Bool) -> (Int) -> Int {
    func stepForward(input: Int) -> Int { return input + 1 }
    func stepBackward(input: Int) -> Int { return input - 1 }
    return backwards ? stepBackward : stepForward
}
// stepForward, stepBackward 는 이 함수 바깥에서 부를 수 없다


'Swift 공식 가이드 > Swift 2' 카테고리의 다른 글

Enumerations  (0) 2016.03.15
Closures  (0) 2016.03.12
Control Flow  (0) 2016.03.03
Collection Types - Dictionary  (0) 2016.03.01
Collection Types - Set  (0) 2016.03.01
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함