[Swift] 문법4 - 함수와 클로저

LeeEunJae·2023년 3월 17일
0

iOS

목록 보기
5/14

📌 함수와 클로저

함수는 func 키워드를 사용해 정의하고, -> 키워드를 사용해서 함수의 리턴 타입을 지정합니다.

func hello(name: String, time: Int) -> String {
    var message = ""
    for _ in 0..<time {
        message += "\(name)님 안녕하세요!\n"
    }
    return message
}

print(hello(name: "은재", time: 3))

Swift 에서는 특이하게도(귀찮게도...) 함수를 호출할 때 파라미터 이름을 함께 써줘야 합니다.

만약, 호출할 때 사용하는 파라미터 이름과 함수 내부에서 사용하는 파라미터 이름을 다르게 사용하고 싶다면, 다음과 같이 작성할 수 있습니다.

func hello(to name: String, numberOfTimes time: Int) -> String {
    var message = ""
    for _ in 0..<time {
        message += "\(name)님 안녕하세요!\n"
    }
    return message
}

print(hello(to: "은재", numberOfTimes: 3))

호출시 파라미터 이름을 생략하고 싶다면, 다음과 같이 파라미터 이름을 _ 로 지정합니다.

func hello(_ name: String, _ time: Int) -> String {
    var message = ""
    for _ in 0..<time {
        message += "\(name)님 안녕하세요!\n"
    }
    return message
}

print(hello("은재", 3))

파라미터 값에 디폴트 값을 줄 수도 있습니다.
디폴트 값이 존재하는 파라미터는 호출시 생략 가능합니다.

func hello(_ name: String, _ time: Int = 3) -> String {
    var message = ""
    for _ in 0..<time {
        message += "\(name)님 안녕하세요!\n"
    }
    return message
}

print(hello("은재"))

... 를 사용하면 개수가 정해지지 않은 파라미터 Variadic Parameters 를 받을 수 있습니다.

func sum(_ numbers: Int...) -> Int {
    var sum = 0
    for number in numbers {
        sum += number
    }
    return sum
}

print(sum(1,2,3,4,5))
print(sum(1,2,3))

함수 안에 함수를 작성할 수도 있습니다.

func hello(name: String, time: Int) {
    func message(name: String) -> String {
        return "\(name)님 안녕하세요!"
    }
    
    for _ in 0..<time {
        print(message(name: name))
    }
}

hello(name: "은재", time: 3)

심지어 함수안에 정의한 함수를 반환하는 함수를 만들 수도 있습니다.

func helloGenerator(message: String) -> (String) -> String {
    func hello(name: String) -> String {
        return name + message
    }
    return hello
    
}

let helloFunc = helloGenerator(message: "님 안녕하세요")
print(helloFunc("은재"))

여기서 핵심은 helloGenerator 라는 함수의 반환 타입이 (String) -> String 이라는 것입니다.
즉, helloGenerator 는 '문자열을 받아서 문자열을 리턴하는 함수' 를 반환하는 함수 입니다.

만약 함수안의 함수가 여러 파라미터를 받는다면 아래와 같이 작성해야 합니다.

func helloGenerator(message: String) -> (String, String) -> String {
    func hello(firstName: String, lastName: String) -> String {
        return lastName + firstName + message
    }
    return hello
    
}

let helloFunc = helloGenerator(message: "님 안녕하세요")
print(helloFunc("은재", "이"))

hello 라는 함수 내부 함수가 파라미터로 문자열을 두개 받으므로 helloGenrator 의 반환 타입은 '문자열 두개를 받아 문자열을 리턴하는 함수'를 반환하는 함수 입니다.
따라서 helloGenrator의 리턴 타입은 (String, String) -> String 이 되겠죠.

📌 클로저(Closure)

클로저를 사용하면 위에 작성한 코드를 좀 더 간결하게 작성할 수 있습니다.
클로저는 중괄호 ( { } ) 로 감싸진 '실행 가능한 코드 블럭' 입니다.

func helloGenerator(message: String) -> (String, String) -> String {
    return { (firstName: String, lastName: String) -> String in
        return lastName + firstName + message
    }
    
}

let helloFunc = helloGenerator(message: "님 안녕하세요")
print(helloFunc("은재", "이"))

함수와는 다르게 함수 이름 정의가 따로 존재하지 않습니다. 하지만 파라미터를 받을 수 있고, 반환 값이 존재할 수 있다는 점에서 함수와 동일합니다. 혹시 눈치채셨나요? 함수는 이름이 있는 클로저입니다.

위 함수에서 클로저를 반환하는 코드를 조금 더 자세히 살펴볼까요? 클로저는 중괄호({})로 감싸져있습니다. 그리고 파라미터를 괄호로 감싸서 정의하고요. 함수와 마찬가지로 ->를 사용해서 반환 타입을 명시합니다. 조금 다른 점은 in 키워드를 사용해서 파라미터, 반환 타입 영역과 실제 클로저의 코드를 분리하고 있습니다.

{ (firstName: String, lastName: String) -> String in
  return lastName + firstName + message
}

얼핏 봐서는 간결하다는 느낌이 들지 않죠? 클로저의 장점은 사실 간결함과 유연함에 있습니다.
생략 가능한 부분들을 모두 생략하고 나면, 매우 간단한 코드를 만들 수 있습니다.
하나씩 생략해볼게요

Swift 컴파일러의 타입 추론 덕분에, helloGenerator 에서 반환 타입으로 파라미터의 타입을 정의해놨기 때문에 클로저에서 어떤 타입의 파라미터를 받고 어떤 타입을 리턴하는지 알 수 있습니다.
과감하게 생략해버리겠습니다.

func helloGenerator(message: String) -> (String, String) -> String {
    return { firstName, lastName in
         return lastName + firstName + message
    }
}

훨씬 깔끔해졌네요. 놀라운 점은 여기서 더 생략가능한 부분이 존재합니다!
마찬가지로 타입추론 덕분에 첫번째 파라미터와 두번째 파라미터 모두 문자열이라는 것을 알 수 있습니다. 따라서 첫번째 파라미터는 $0, 두번재 파라미터는 $1로 표현할 수 있습니다.

func helloGenerator(message: String) -> (String, String) -> String {
    return {
         return $1 + $0 + message
    }
}

클로저 내부의 코드가 한 줄이라면, return 역시 생략 가능합니다.

func helloGenerator(message: String) -> (String, String) -> String {
    return { $1 + $0 + message }
}

코드가 달랑 한 줄로 줄어들었네요. 이제 정말 간결해졌죠?

클로저는 변수처럼 정의할 수도 있습니다.

let hello : (String, String) -> String = { $1 + $0 + "님 안녕하세요!" }
print(hello("은재", "이"))

물론 옵셔널로 정의할 수 있고, 옵셔널 체이닝도 가능합니다.

let hello : ((String, String) -> String)? = { $1 + $0 + "님 안녕하세요!" }
print(hello?("은재", "이"))

클로저를 변수로 정의하고, 함수에서 반환할 수 있는 것 처럼 파라미터로 받을 수 있습니다.

func manipulate(number: Int, block: (Int) -> Int) -> Int {
    return block(number)
}

var result = manipulate(number: 3, block: { receivedNumber in
    receivedNumber * 2
})

print(result) // 6

생략할 수 있는 부분들이 보이지만, 생략하는 것 보다 이게 좀 더 명확해보여서 마음에 듭니다.
(코틀린스러운 것 같기도 하고,,,)

코틀린과 마찬가지로 클로저가 마지막 파라미터라면, 괄호와 파라미터 이름 생략이 가능합니다.

var result = manipulate(number: 3) { receivedNumber in
    receivedNumber * 2
}

이렇게 보니 한 층 코틀린스러워서 마음에 드네요 ㅎㅎ

이런 클로저 구조로 만들어진 예시가 바로 sort() 와 filter() 입니다. 파라미터로 클로저 하나만 받는다면, 괄호 조차 쓰지 않아도 됩니다!

let numbers = [1,3,2,7,6,9,4,8,5]

let sortedNumbersAsc = numbers.sorted { previous, next in
    previous < next
} // 오름차순
let sortedNumbersDesc = numbers.sorted { previous, next in
    previous > next
} // 내림차순
print(sortedNumbersAsc)
print(sortedNumbersDesc)

let evenNumbers = numbers.filter { number in
    number % 2 == 0
} // 짝수
print(evenNumbers)

역시 생략 가능한 부분들이 있으나 전 이게 좋아요...!
앞으로 iOS 개발 할 때에도 가독성을 위해 이렇게 사용할 것 같습니다.

클로저는 이 외에도 많은 곳에서 사용되는데 (map, reduce 등) 나머지는 직접 사용해보면 될 것 같습니다.

profile
매일 조금씩이라도 성장하자

0개의 댓글