Swift 함수와 클로저: 재사용 가능한 코드 블록 만들기

지난 블로그에는 프로그램의 의사 결정과 반복을 담당하는 조건문과 반복문에 대해 다루었습니다. 

이제 우리는 코드를 더 효율적이고 체계적으로 관리할 수 있는 다음 단계, 함수(Functions)클로저(Closures)의 세계로 떠나볼 것입니다.

함수와 클로저는 프로그램에서 특정 작업을 수행하는 코드 덩어리를 묶어 이름을 붙이고, 필요할 때마다 호출해서 사용할 수 있게 해주는 강력한 도구입니다. 이들을 잘 활용하면 코드를 재사용하기 쉬워지고, 가독성이 높아지며, 유지보수도 훨씬 편리해집니다.


1. 함수 (Functions): 코드의 재활용 도우미

함수는 특정 작업을 수행하는 독립적인 코드 블록입니다. 우리가 일상생활에서 어떤 '기능'을 가진 도구를 사용하듯이, 프로그램 안에서 특정 기능을 수행하도록 정의된 코드의 묶음이라고 생각할 수 있습니다. Swift에서 함수를 정의할 때는 func 키워드를 사용합니다.

💡 함수를 사용하는 이유:

  • 코드 재사용: 같은 작업을 여러 번 해야 할 때, 코드를 복사 붙여넣기하는 대신 함수를 한 번 정의해두고 여러 번 호출하여 사용합니다.
  • 코드 조직화: 복잡한 프로그램을 작은 기능 단위로 나누어 관리하기 쉽게 만듭니다.
  • 가독성 향상: 함수의 이름만 보고도 어떤 작업을 하는지 유추할 수 있어 코드 이해가 쉬워집니다.

1.1. 함수의 기본 구조

Swift 함수의 기본 구조는 다음과 같습니다.

func 함수이름(매개변수1: 타입, 매개변수2: 타입) -> 반환타입 {
    // 함수가 수행할 작업 코드
    return 반환값 // 반환타입이 있을 경우
}

  • func: 함수를 정의할 때 사용하는 키워드입니다.
  • 함수이름: 함수를 호출할 때 사용할 이름입니다. (예: greet, addNumbers)
  • 매개변수(Parameters): 함수가 작업을 수행하는 데 필요한 입력 값들입니다. 괄호 () 안에 쉼표로 구분하여 나열합니다. 각 매개변수는 이름과 타입을 가집니다.
  • -> 반환타입(Return Type): 함수가 작업을 마치고 돌려주는 값의 타입입니다. 함수가 값을 반환하지 않으면 이 부분을 생략할 수 있습니다.
  • return: 함수가 값을 반환할 때 사용하는 키워드입니다.

// 1. 매개변수 없고, 반환값 없는 함수
func sayHello() {
    print("안녕하세요, Swift 함수!")
}
sayHello() // 함수 호출: 안녕하세요, Swift 함수!

// 2. 매개변수 있고, 반환값 없는 함수
func greet(name: String) {
    print("안녕, \(name)! 만나서 반가워.")
}
greet(name: "배팀장") // 함수 호출: 안녕, 배팀장! 만나서 반가워.

// 3. 매개변수 있고, 반환값 있는 함수
func addNumbers(num1: Int, num2: Int) -> Int {
    let sum = num1 + num2
    return sum
}
let result = addNumbers(num1: 10, num2: 20)
print("두 숫자의 합: \(result)") // 출력: 두 숫자의 합: 30

1.2. 매개변수 레이블 (Argument Labels)

Swift 함수는 함수를 호출할 때 사용하는 매개변수 레이블(Argument Labels)과 함수 내부에서 사용하는 매개변수 이름(Parameter Names)을 가질 수 있습니다. 기본적으로 매개변수 이름이 레이블로 사용되지만, 명시적으로 다르게 지정할 수도 있습니다.


func sendMessage(to recipient: String, message: String) {
    print("To \(recipient): \(message)")
}

// 호출 시 매개변수 레이블 사용: to, message
sendMessage(to: "김철수", message: "오늘 저녁에 볼까?")
// 출력: To 김철수: 오늘 저녁에 볼까?

// 매개변수 레이블을 생략하고 싶다면 '_' 사용
func printMessage(_ msg: String) { // _로 레이블 생략
    print(msg)
}
printMessage("레이블 없이 호출!") // 출력: 레이블 없이 호출!
sendMessage(to:message:)와 같이 매개변수 레이블을 사용하면 함수 호출 시 가독성이 높아집니다.

2. 클로저 (Closures): 유연한 코드 블록 

클로저는 독립적인 기능을 하는 코드 블록으로, 주변 환경의 상수나 변수를 캡처(Capture)하여 저장할 수 있습니다. 
함수는 이름이 있는 클로저의 특별한 형태라고 볼 수 있습니다. 
즉, 모든 함수는 클로저이지만, 모든 클로저가 함수인 것은 아닙니다. 
클로저는 이름이 없는 함수(무명 함수)처럼 사용되기도 합니다. 

 💡 클로저를 사용하는 이유: 
  • 코드의 간결성: 
    • 짧은 코드 블록을 즉석에서 정의하고 사용할 때 매우 유용합니다. 
  • 비동기 처리: 
    • 네트워크 통신 후 결과를 처리하거나, 애니메이션 완료 시 특정 작업을 수행하는 등 비동기 작업에서 콜백(callback)으로 많이 사용됩니다.
  • 고차 함수와 함께 사용: 
    • map, filter, sorted 등 배열이나 컬렉션을 다루는 고차 함수와 함께 사용될 때 강력한 시너지를 냅니다. 

2.1. 클로저의 기본 형태 

클로저는 중괄호 {}로 묶이며, 다음과 같은 형태를 가집니다.


{ (매개변수들) -> 반환타입 in
    // 클로저가 수행할 작업 코드
    return 반환값 // 반환타입이 있을 경우
}

  • in 키워드는 매개변수 및 반환 타입 정의와 클로저 본문 코드를 구분합니다.

Swift 코드 예시:
// 1. 가장 기본적인 클로저 예시
let simpleClosure = {
    print("이것은 간단한 클로저입니다.")
}
simpleClosure() // 클로저 호출: 이것은 간단한 클로저입니다.

// 2. 매개변수와 반환값이 있는 클로저
let sumClosure: (Int, Int) -> Int = { (a: Int, b: Int) -> Int in
    return a + b
}
let total = sumClosure(5, 7)
print("클로저로 계산한 합: \(total)") // 출력: 클로저로 계산한 합: 12

// 3. 클로저 표현식의 간략화 (Swift의 강력한 기능!)
// 3.1. 타입 추론
let multiplyClosure: (Int, Int) -> Int = { (a, b) in
    return a * b
}
print("클로저로 계산한 곱: \(multiplyClosure(3, 4))") // 출력: 클로저로 계산한 곱: 12

// 3.2. 단일 표현식 암시적 반환 (return 생략)
let subtractClosure: (Int, Int) -> Int = { (a, b) in
    a - b
}
print("클로저로 계산한 차: \(subtractClosure(10, 3))") // 출력: 클로저로 계산한 차: 7

// 3.3. 인자 이름 축약 ($0, $1 등)
let divideClosure: (Int, Int) -> Int = { $0 / $1 } // $0은 첫 번째 인자, $1은 두 번째 인자
print("클로저로 계산한 나눗셈: \(divideClosure(20, 4))") // 출력: 클로저로 계산한 나눗셈: 5

2.2. 후행 클로저 (Trailing Closures)

함수의 마지막 매개변수가 클로저이고, 그 클로저가 길 때 소괄호 () 밖에 클로저를 작성하는 문법입니다.
코드를 훨씬 더 깔끔하고 읽기 쉽게 만듭니다. SwiftUI에서 UI를 구성할 때 자주 보게 될 형태입니다.


// 일반적인 클로저 전달 방식
func doSomething(completion: () -> Void) {
    print("작업 시작...")
    completion() // 작업 완료 후 클로저 실행
}
doSomething(completion: {
    print("작업 완료!")
})

// 후행 클로저 사용 방식 (더 간결!)
func doAnotherSomething(completion: () -> Void) {
    print("다른 작업 시작...")
    completion()
}
doAnotherSomething { // 소괄호 밖으로 클로저 이동
    print("다른 작업 완료!")
}

정리하며

오늘은 Swift에서 코드의 재사용성과 효율성을 극대화하는 함수와 클로저에 대해 심층적으로 알아보았습니다.

  • 함수: 특정 작업을 수행하는 이름 있는 코드 블록으로, 코드의 조직화와 재사용에 핵심적입니다.
  • 클로저: 주변 환경의 값을 캡처할 수 있는 유연한 코드 블록으로, 특히 비동기 처리나 고차 함수와 함께 강력한 시너지를 냅니다.

함수와 클로저는 Swift 프로그래밍의 기반을 이루는 매우 중요한 개념입니다. 다양한 예시를 직접 작성하고 실행해보면서 이들의 동작 방식을 완벽하게 이해하는 것이 중요합니다.

다음 시간에는 Swift의 옵셔널(Optional) 개념을 다룰 예정입니다. nil 값으로부터 안전하게 코드를 작성하는 Swift만의 독특하고 필수적인 기능을 함께 살펴보겠습니다.



댓글