본문 바로가기

Swift

Functions and Closures - A Swift Tour 2일차

반응형

func을 사용하여 함수를 선언한다. 다음과 같이 함수 이름과 인자 목록을 사용하여 함수를 호출한다. ->를 사용하여 함수의 반환형을 명시해준다.

func gree(person: String, day: String) -> String {
    return "Hello \(person), today is \(day)."
}
greet(person: "Bob", day: "Tuesday")

기본적으로 함수는 그들의 파라미터 이름을 인자에 대한 라벨로 사용한다. 파라미터 이름 전에 커스텀 라벨을 적어 사용할 수 있다. 혹은 _를 사용하여 인자 라벨을 사용하지 않을 수 있다.

func greet(_ person: String, on day: String) -> String {
    return "Hello \(person), today is \(day)."
}
// before: greet(person: "John", day: Wednesday")
greet("John", on: Wednesday")

튜플을 사용하여 복잡한 값을 만들 수 있다. 예를 들어, 함수의 반환값으로 여러 개의 값을 반환할 수 있다. 튜플의 요소는 이름이나 번호로 참조될 수 있다.

func calculateStatistics(scores: [int]) -> (min: Int, max: Int, sum: Int) {
    var min = scores[0]
    var max = scores[0]
    var sum = 0

    for score in scores {
        if score > max {
            max = score
        } else if score < min {
            min = score
        }
        sum += score
    }

    return (min, max, sum)
}
let statistics = calculateStatistics(scores: [5, 3, 100, 3, 9])
print(statistics.sum)
// prints "120"
print(statistics.2)
// prints "120"

함수는 다른 함수 안에 속할 수 있다. 이를 nested function이라고 한다. nested function은 바깥 함수에서 정의된 변수에 접근하는 게 가능하다. 함수의 길이가 길어지거나 복잡해질 때, nested function을 사용할 수 있다.

func returnFifteen() -> Int {
    var y = 10
    func add() {
        y += 5
    }
    add()
    return y
}
returnFifteen()

함수는 first-class 타입이다. 다시 말해, 함수는 다른 함수를 결과값으로 반환할 수 있다. 아래 예시를 보면 makeIncrementer 함수의 반환형이 ((Int) -> Int), 즉 Int를 받아 Int를 반환하는 함수의 형태라는 것을 알 수 있다. makeIncrementer 함수 안에는 ((Int) -> Int) 형태의 함수 addOne이 존재하며, 해당 함수를 반환하는 것이다. var increment는 ((Int) -> Int) 타입의 변수이며, 숫자를 넣어 호출할 수 있다. 이때, 라벨을 붙이지 않아야 한다.

func makeIncrementer() -> ((Int) -> Int) {
    func addOne(number: Int) -> Int {
        return 1 + number
    }
    return addOne
}
var increment = makeIncrementer()
increment(7)

사실 위 코드는 다음과 다를 바 없다.

func addOne(number: Int) -> Int {
    return 1 + number
}

var increment = addOne
increment(7)

그러나 outer function의 변수에 접근할 수 있는 nested function의 특징을 적용한다면 다음과 같이 함수를 만들 수도 있다.

func makeCounter(base: Int) -> (() -> Int) {
    var count = base
    func addOne() -> Int {
        count += 1
        return count
    }
    return addOne
}
var counter = makeCounter(base: 100)
counter()    // 101
counter()    // 102

함수는 다른 함수를 하나의 인자로 받을 수도 있다. 라벨을 붙이지 않아야 하는 이유가 여기서 드러난다. 다음과 같이 함수를 인자로 받아 사용하는 경우, 라벨에 상관없이 사용이 가능해야 하기 때문이다.

func hasAnyMatches(list: [Int], condition: (Int) -> Bool) -> Bool {
    for item in list {
        if condition(item) {
            return true
        }
    }
    return false
}

func lessThanTen(number: Int) -> Bool {
    return number < 10
}

var numbers =  [20, 19, 7, 12]
hasAnyMatches(list: numbers, condition: lessThanTen) // true

함수는 실제론 클로저의 일종이다: 나중에 호출될 수 있는 코드 덩어리. 클로저의 코드는 (클로저가 다른 스코프에서 실행되더라도) 클로저가 생성된 스코프 내에 존재하는 변수와 함수에 접근 가능하다. - 이러한 예시는 이미 nested function에서 보인 바 있다. 코드를 {}로 감싸 클로저를 이름 없이 사용할 수 있다. in

numbers.map({ (number: Int) -> Int in
    let result = 3 * number
    reture result
})

클로저를 더 정확하게 작성하는 방법은 여러 가지가 있다. delegate로 인한 콜백 함수 등으로 클로저의 타입이 이미 알려진 경우,
파라미터의 타입이나 반환형 타입을 적지 않아도 괜찮다.

let mappedNumbers1 = numbers.map({ (number: Int) -> Int in
    reture 3 * number
})
// [60, 57, 21, 36]

let mappedNumbers2 = numbers.map({ number in 3 * number })
// [60, 57, 21, 36]

파라미터를 이름 대신 번호로 호출할 수도 있다. 이러한 접근법은 매우 짧은 클로저를 사용할 때 유리하다. 클로저가 함수의 마지막 인자로 사용될 때는 클로저 자체를 () 괄호 밖으로 내보낼 수 있다. 최종적으로 클로저는 다음과 같이 축약할 수 있다.

let mappedNumbers3 = numbers.map { 3 * $0 }
print(mappedNumbers3)    // [60, 57, 21, 36]

let sortedNumbers = numbers.sorted { $0 > $1 }
print(sortedNumbers)    // [20, 19, 12, 7]
반응형