클로저(Closure)
Swift를 공부하면서 만나는 첫번째 보스는 클로저다
개발할 때 많이 사용된다고 하기도 하고, 코드를 작성하면서 자주 보기도 한다.

대략 이런 느낌.....
클로저를 쉽게 이해할 수 있는 방법이 있을까?
사실 그런 방법은 없지만, 최대한 디테일하게 정리하면서 공부해보고자 한다.
⎮ 클로저가 뭔데?
클로저는 명명된 함수(이름을 가지고 있는 함수) 생성 없이 실행되는 코드 그룹이다.
'클로저 = 이름이 없는 함수'처럼 생각하라고 공식처럼 되어 있어서
함수의 하위 개념이 클로저인가 하고 생각할 수 있는데, 오히려 함수가 이름이 있는 클로저다다.
어렵게 쓰여있지만, "123"이 String Type인 것처럼,
클로저도 Type이라 생각하면 된다. () -> () Type이다.
* 물론 파라미터가 없고 리턴이 없을 때만 () -> ()
⎮ 클로저 기본 형태
// 함수의 형태
func 함수 이름(매개변수) -> 반환타입 {
}
// 클로저의 형태
{ (매개변수) -> 반환타입 in
실행할 코드
}
앞서 봤던 클로저의 기본 설명처럼,
클로저는 func 키워드와 함수 이름이 없고,
반환 타입에 in 키워드와 실행할 코드를 가지고 있는 형태로 되어 있다.
숫자를 입력하면 String Type으로 바꿔주는 함수가 있다.
이 함수를 클로저로 바꿔주려면
func 키워드와 함수 이름을 지운다음 남은 부분을 { } 안에 넣어주면 된다.
// 함수
func changeTypeString(number: Int) -> String {
return "\(number)"
}
// 클로저
{ (number: Int) -> String in
return "\(number)"
}
⎮ 클로저 캡처
클로저는 값을 캡처할 수 있다.
클로저는 주변 변수를 기억할 수 있고, 나중에 클로저가 실행될 때에도 그 값을 계속 사용할 수 있다.
func makeCounter() -> (() -> Int) {
var count = 0
return {
count += 1
return count
}
}
let counter = makeCounter()
print(counter()) // 1
print(counter()) // 2
클로저로 리턴을 할 때 해당 값을 캡처해서 담아두면,
함수를 실행할 때마다 count의 값을 기억하기 때문에, 지속적으로 값을 증가시킬 수 있음 (단 var로 선언했을 때만)
⎮ 클로저의 특징1 : 할당이 가능하다
1-1. 변수에 할당하기
이렇게 만들어진 클로저는 변수에 할당이 가능하다.
// 클로저
var typeChangeString = { (number: Int) -> String in
return "\(number)"
}
클로저의 호출은 함수 실행과 동일하게 할 수 있다.
// 함수
func changeTypeString(number: Int) -> String {
return "\(number)"
}
changeTypeString(number: 10)
// 클로저
var typeChangeString = { (number: Int) -> String in
return "\(number)"
}
typeChangeString(10)
또한 변수에 클로저를 담으면 타입이 동일한 클로저로 값을 변경할 수 있다.
// 타입이 동일하면 변경이 가능하다 : 클로저 (Int) -> (String)
var typeChangeString = { (number: Int) -> String in
return "\(number)"
}
typeChangeString = { (studentNumber: Int) -> String in
return "\(studentNumber)"
}
// 타입이 같다면 함수를 클로저에 대입할 수도 있음
typeChangeString = changeTypeString
/*
typeChangeString의 Type은 (Int) -> String 이며,
changeTypeString의 Type 또한 (Int) -> String으로
Type이 같이 때문에 대입할 수 있음
*/
1-2. 매개변수에 할당하기
함수의 매개변수에 클로저를 담을 수 있다.
* completion Handler로 주로 사용(코드 실행이 완료되고 나서 무언가를 할 때)
// 함수의 매개변수로 함수를 받을 수 있음
func changeTypeString(change: () -> Void) {
change()
}
// 실행할 함수
func change() {
print("타입이 변경되었습니다.")
}
// 함수 호출
changeTypeString {
change()
}
// 클로저를 변수에 저장
var typeChangeString = {
print("타입이 변경되었습니다.")
}
// 함수의 매개변수로 클로저를 받을 수 있음(보통 이렇게 사용하지는 않고, 다음에 오는 코드처럼 사용)
changeTypeString (change: typeChangeString)
// 보통은 이렇게 사용한다 (Trailing Closure)
changeTypeString {
typeChangeString()
}
// 함수의 매개변수로 클로저를 받으면 그때그때 코드를 다르게 실행할 수도 있음
func calculate(number1: Int, number2: Int, operation: (Int, Int) -> Void) {
operation(number1, number2)
}
// 예를 들면, 이때는 더하기
calculate(number1: 3, number2: 5) { number1, number2 in
print(number1 + number2)
}
// 이때는 빼기
calculate(number1: 6, number2: 10) { number1, number2 in
print(number1 - number2)
}
// 클로저는 별도로 실행되는 것으로 클로저의 매개변수에 꼭 함수의 파라미터를 써줘야하는 것은 아님
calculate(number1: 6, number2: 10) { num1, num2 in
print(num1 * num2)
}
따라서 함수를 실행할 때 파라미터로 클로저를 받을 수 있는 특성을 이용해서,
비동기 작업을 할 때 클로저를 자주 사용한다.
// 함수 실행 2초 후 코드 블럭에 있는 코드를 실행하기
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
let navigationController = UINavigationController(rootViewController: rootVC)
self.window?.rootViewController = navigationController
}
* completion Handler
// Completion Handler의 예시
func setupView() {
let vc = UIViewController()
vc.dismiss(animated: true, completion: (() -> Void)?)
}
// 예시 코드
func setUpView() {
let vc = UIViewController()
// 뷰가 사라지고나서 text를 변경하는 클로저를 실행할 수 있음
vc.dismiss(animated: true) {
changeText()
}
}
// 텍스트를 변경해주는 클로저
var changeText = {
label.text = "텍스트가 바뀌었습니다."
}
1-3. Return Type에 할당하기
// 클로저를 리턴하는 함수
func addClosure(first: Int) -> ((Int) -> Void) {
return { second in
let result = first + second
print("result: \(result)")
}
}
let closure = addClosure(first: 8)
closure(5)
⎮ 클로저의 특징2 : 축약이 가능하다
- Swift Type 추론을 이용한 생략
// Int에서의 Type 추론
var number: Int = 123 // 이 코드는 아래와 동일한 코드
var number = 123 // 여기서 Int type을 명시해주지 않아도 Swift는 Type을 추론함
// Closure에서의 Type 추론 : Return Type을 명시해주지 않아도 Return하는 값이 있으면 Type 추론이 가능
var typeChangeString = { (number: Int) in
return "\(number)"
}
- 암시적 반환 : return 키워드 생략(return이 코드 블럭 내 유일한 코드일 때)
//함수에서의 생략
func changeTypeString(number: Int) -> String {
return "\(number)" // 여기서 return을 쓰지 않아도, return이 가능함
}
func changeTypeString(number: Int) -> String {
"\(number)" // 이렇게 하면 위와 동일한 코드
}
// 클로저에서의 생략
var typeChangeString = { (number: Int) in
"\(number)" // 마찬가지로 클로저에서도 return을 생략할 수 있음
}
// 위의 코드를 정리하여 아래의 코드처럼 작성 가능(다만, 이렇게 사용하지는 않음)
var typeChangeString = { (number: Int) in "\(number)" }
⎮ 클로저의 특징3 : 축약 인자를 가지고 있다
Swift는 인라인 클로저에 $0, $1 등으로 대체하여 사용할 수 있는 축약 인자(Shorthand Argument Names를 제공한다.
짧은 인수 이름의 수와 타입은 함수 타입에서 유추되며, 클로저 표현식 자체가 본문으로 구성되기 때문에 in 키워드의 생략이 가능하다
// 완전한 축약 형태
var typeChangeString = { "\($0)" }
// 파라미터에 해당하는 축약 인자가 있음
func calculate(number1: Int, number2: Int, add: (Int, Int) -> Void) {
add(number1, number2)
}
calculate(number1: 3, number2: 5) {
print($0 + $1)
}
그러면 이러한 클로저의 특징은 어디에서 나온걸까?
클로저는 일급 시민 객체이기 때문에 위의 특징을 가지고 있다.
⎮ 일급 시민 객체(First-Class-Citizen)
- 변수나 상수에 할당이 가능하다(Type이 있기 때문에)
* 클로저나 함수도 () -> () 형태의 Type
// string이 Type이기 때문에 변수에 담을 수 있음
var stringNumber = "123"
// 마찬가지로 int도 Type이기 때문에 담을 수 있음
var number = 123
// 함수도 () -> () Type이므로 담을 수 있고,
var myFunction = printMyName
func printMyName() {
print("당신의 이름은 홍길동입니다.")
}
myFunction()
// closure도 () -> () Type이기 때문에 담을 수 있음
var yourFunction = {
print("당신의 이름은 고길동입니다")
}
그렇다면 이런 형식도 가능하지 않을까?
myFunction = yourFunction
(이 부분은, 간단하게 클로저를 공부하려고 하는 사람은 아래의 방법으로 해결하고 넘어가면 된다.)
// 변수에 함수를 할당 : myFunction의 Type은 () -> ()
var myFunction = printMyName
func printMyName() {
print("당신의 이름은 홍길동입니다.")
}
// 클로저의 타입 : yourFunction의 Type도 () -> ()
var yourFunction = {
print("당신의 이름은 고길동입니다")
}
// myFunction에 클로저도 할당할 수 있겠다?
myFunction = yourFuntion // 에러 발생
myFunction = yourFunction을 하면 하단의 에러가 발생하고,
Converting non-sendable function value to '@Sendable () -> ()' may introduce data races
이를 해결하는 방법은 myFunction의 타입을 명시적으로 () -> ()으로 선언해주면 된다.
// myFunction의 type을 명시적으로 () -> ()으로 선언
var myFunction: () -> () = printMyName
func printMyName() {
print("당신의 이름은 홍길동입니다.")
}
// 클로저의 타입 : yourFunction의 Type도 () -> ()
var yourFunction = {
print("당신의 이름은 고길동입니다")
}
// myFunction에 클로저 할당 가능
myFunction = yourFuntion // 에러 해결
* 위의 오류가 더 궁금하다면, 포스팅 맨 하단의 링크를 참고하면 된다.
- 함수의 매개변수로 사용이 가능하다
// 함수의 매개변수로 String을 받을 수 있음
func changeTypeInt(strNumber: String) {
print(strNumber)
}
// 위와 마찬가지로 함수의 매개변수로 함수도 받을 수 있음
func changeTypeString(change: () -> Void) {
change()
}
// 실행할 함수
func change() {
print("타입이 변경되었습니다.")
}
// 함수 호출
changeTypeString {
change()
}
// 위와 마찬가지로 함수의 매개변수로 클로저도 받을 수 있는 것
func calculate(number1: Int, number2: Int, operation: (Int, Int) -> Void) {
operation(number1, number2)
}
- 다른 함수의 반환 값으로 사용이 가능하다
// 함수의 매개변수로 Int를 받고 Int를 리턴할 수 있음
func getNumber(number: Int) -> Int {
return number
}
// 함수의 매개변수로 함수를 리턴할 수 있음
func makeGreeter() -> () -> Void {
func greeter() {
print("안녕하세요! 반갑습니다")
}
return greeter
}
let greetingFunction = makeGreeter() // 여기서 함수를 리턴 받음
greetingFunction()
// 안녕하세요 반갑습니다 출력
// 마찬가지로 함수의 매개변수로 클로저도 리턴할 수 있음
func addClosure(first: Int) -> ((Int) -> Void) {
return { second in
let result = first + second
print("result: \(result)")
}
}
let closure = addClosure(first: 8)
closure(5)
// result: 13
⎮ Swift Docs 클로저
[Swift Docs 클로저 - 영문 ver]
https://docs.swift.org/swift-book/documentation/the-swift-programming-language/closures
Documentation
docs.swift.org
[Swift Docs 클로저 - 한글 ver]
https://bbiguduk.gitbook.io/swift/language-guide-1/closures
클로저 (Closures) | Swift
명명된 함수 생성없이 실행되는 코드 그룹입니다. 클로저 (Closures) 는 코드에서 주변에 전달과 사용할 수 있는 자체 포함된 기능 블럭입니다. Swift의 클로저는 다른 프로그래밍 언어에서 클로저,
bbiguduk.gitbook.io
⎮ 오류 알아보기
Converting non-sendable function value to '@Sendable () -> ()' may introduce data races
위 오류가 더 궁금하신 분은, 아래의 포스팅으로... (저와 같은 초보자는 누르지 마세요...)
https://uddt.tistory.com/202
Swift | Converting non-sendable function value to '@Sendable () -> ()' may introduce data races 오류 이해하기
⎮ 주의 이 오류는 Swift5에서는 뜨지 않고, Swift6에서 뜬다. (서치해보니, @Sendable 이라는 개념이 Swift 5.5부터 나온 것 같다) 아마 Swift 5에서는 경고로만 나오거나, 아예 나오지 않을 수
uddt.tistory.com
'스파르타코딩 클럽 > 기초' 카테고리의 다른 글
| Swift | do, catch, try 이해하기(feat. Result Type) (0) | 2025.04.20 |
|---|---|
| Swift | 네트워크 쉽게 이해하기(with URLSession) (0) | 2025.04.17 |
| Swift | MVC 패턴이 뭔데? (0) | 2025.04.02 |
| Swift | Delegate 패턴이 뭔데? (0) | 2025.04.02 |
| 객체지향 프로그래밍이 뭔데? + SOLID 원칙 (0) | 2025.03.23 |