안녕하세요. 오늘은 클로저에 대해 알아보도록 하겠습니다!
클로저란?
독립적으로 실행 가능한 코드 블럭으로 이름 없는 익명 함수라고 볼 수 있습니다.
함수와 유사한 기능을 제공하지만 함수와 달리 직접적인 이름을 가지진 않습니다.
func add(a: Int, b: Int) -> Int {
return a + b
}
// 함수
let add: (Int, Int) -> Int = { (a, b) in
return a + b
}
// 클로저
클로저는 다음과 같이 중괄호 '{}'로 둘러싸인 코드 블럭이며 함수와 비슷한 모습을 하고 있습니다.
위 코드에서는 편의를 위해 클로저를 'add'라는 상수에 할당한 모습입니다.
클로저 특징
1. 변수 또는 상수 캡처
클로저가 생성된 시점에서 주변 범위에 있는 변수나 상수의 값을 캡쳐하며, 나중에 해당 값을 변경하거나 참조할 수 있습니다.
즉, 원본 값이 사라져도 클로저의 안에서 그 값을 활용할 수 있습니다.
Swift에서 값을 캡쳐하는 가장 단순한 형태는 중첩 함수 (nested function) 입니다.
중첩 함수는 함수 내부에 또 다른 함수를 정의하고 사용하는 것을 의미합니다.
2. 일급 객체
클로저는 일급 객체 (First-Class Object)로 취급됩니다.
일급 객체란 다음과 같은 특징을 가집니다.
1. 변수나 상수에 할당할 수 있다.
func greeting(name: String) -> String {
return "\(name)님 환영합니다"
}
let greetClient = greeting
print(greetClient) // (Function)
print(greetClient("원빈")) // 원빈님 환영합니다
2. 함수의 전달인자로 전달할 수 있다.
func greeting(name: String) -> String {
return "\(name)님 환영합니다"
}
func executeFunction(passedFunction: (String) -> String) -> String {
return passedFunction("원빈")
}
print(executeFunction(passedFunction: greeting)) // 원빈님 환영합니다
3. 함수의 반환 값으로 사용할 수 있다.
func greeting(name: String) -> String {
return "\(name)님 환영합니다"
}
func returnFunction() -> (String) -> String {
return greeting
}
let function = returnFunction()
print(function("원빈")) // 원빈님 환영합니다
이와 같이 함수를 일급 객체로써 사용하는 언어를 함수형 프로그래밍 언어라고 합니다.
표현식
클로저 표현 문법은 일반적으로 아래와 같습니다.
{ (매개변수) -> 반환타입 in
실행구문
}
let greeting = { (name: String) -> String in
return "\(name)님 환영합니다."
}
print(greeting("원빈")) // 원빈님 환영합니다.
인자로 넣을 파라미터와 인자 값을 가지고 실행할 실행구문 그리고 반환타입입니다.
func greeting(name: String, completion: (String) -> Void) {
completion(name)
}
func printName(name: String) -> Void {
print("\(name)님 환영합니다.")
}
greeting(name:"원빈", completion: printName) // 원빈님 환영합니다.
greeting 함수는 String 타입의 name과 completion이라는 함수를 파라미터로 갖습니다.
호출 시 name을 completion 함수로 전달하는 역할을 합니다.
이와 같은 코드를 아래와 같이 조금 더 직관적이고 간단하게 표현할 수 있습니다.
func greeting(name: String, completion: (String) -> Void) {
completion(name)
}
greeting(name: "원빈"){ (name) in
print("\(name)님 환영합니다.") // 원빈님 환영합니다.
}
아까와 달리 printName이라는 func을 넘겨주지 않는 모습입니다. 어떻게 된걸까요?
클로저가 함수의 마지막 인자로 전달될 경우 함수의 괄호를 닫은 후에 클로저를 작성할 수 있습니다.
이를 trailing closure라고 합니다.
어디서 사용되나요?
만약 배열을 많이 사용하셨다면 이미 클로저를 사용해 보셨을텐데요!
배열을 처리하고 변환하는 map, reduce, filter 와 같은 함수들은 클로저를 인자로 받는 함수입니다.
let numbers = [1, 2, 3, 4, 5]
let squaredNumbers = numbers.map { number in
return number * number
}
let numbers = [1, 2, 3, 4, 5]
let sum = numbers.reduce(0) { result, number in
return result + number
}
let numbers = [1, 2, 3, 4, 5]
let evenNumbers = numbers.filter { number in
return number % 2 == 0
}
위 함수들은 클로저를 인자로 받아서 해당 배열의 각 요소에 적용하고 있습니다.
따라서 함수의 괄호를 닫은 후 trailing closure 형태로 클로저를 정의하고 사용하고 계셨던 겁니다.
또한, 서버와 통신하기 위해 URLSession을 사용하셨다면 클로저를 사용해 보셨을텐데요.
class NetworkHandler: {
func fetchData(resource: String) {
guard let url = URL(string: resource) else { return }
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if let error = error {
print(error.localizedDescription)
return
}
guard let data = data else { return }
do {
data 디코딩 후 원하는 작업 실행
} catch {
print("Error: \(error.localizedDescription)")
}
}
task.resume()
}
}
URLSession을 사용하면 해당 url로 요청을 보내게 됩니다.
URLSession은 비동기적으로 작동하기 때문에 백그라운드에서 실행되고, 작업이 완료되면 completion handler를 호출하게 됩니다.
위의 코드에서 { (data, reponse, error) ... } 부분이 곧 completion handler 이자 data, response, error를 매개변수로 받는 클로저였던 것입니다.
참고
클로저 (Closures) - The Swift Language Guide (한국어)
위 코드는 각 자리수를 구해서 그 자리수를 문자로 변환하고, 10으로 나눠서 자리수를 바꾸며 문자로 변환하는 것을 반복합니다. 이 과정을 통해 숫자 배열을, 문자 배열로 바꿀 수 있습니다. numb
jusung.gitbook.io
'iOS \ Swift' 카테고리의 다른 글
[SwiftUI] page이동 시 TabView 숨기기 (0) | 2023.06.30 |
---|---|
[Swift] The data couldn’t be read because it is missing 에러 해결하기 (0) | 2023.06.29 |
[Swift] if let vs guard let 에 대해 알아보자 (0) | 2023.06.19 |
[Swift] 접근제어 (0) | 2023.06.19 |
[Swift] http request를 통해 data 불러오기 및 사진 업데이트 (0) | 2023.06.18 |