seong_hye, the developer

Swift) 문법 정리 - 클로저 (Closure) 본문

IOS

Swift) 문법 정리 - 클로저 (Closure)

seong_hye 2022. 7. 12.

 

📘 Swift 문법 정리: 클로저(Closure)


Swift 문법 중 클로저에 대해 정리한 글입니다.


Swift를 공부하다 새로운 기술을 많이 보게 되는데

가장 흥미롭게 공부하는데 시간이 꽤 걸린 클로저에 대해 알아보려고 합니다.


 

🔹 문법 설명

클로저를 한마디로 정의한다면 익명함수라고 할 수 있습니다.

함수에서 이름을 땐 기능 블록을 의미합니다.

하지만, 사실은 func 키워드를 이용해 이름이 붙어있는 함수들도 모두 클로저라고 합니다.

 

📌  이름이 있는 클로저(Named Closure) + 이름이 없는 클로저(Unnamed Closure) = 클로저 (Closure)

여기서 우리는 Named Closure 를 함수라고 부르고 있으며
Unnamed Closure를 클로저라고 말합니다.

 

클로저가 함수보다 큰 범위인 것이죠!(포함관계)

 

func doSomthing() { //이름이 있는 클로저(Named Closure)
	print("hi")
}


let closure = { print("Hi") } // 이름이 없는 클로저(Unnamed Closure)

 

일급객체로 취급되는 코드 블록으로 일반 함수처럼 인자와 반환값을 가질 수 있지만 이름 없이 사용할 수 있습니다.

또한 핵심 기능으로 주변의 값을 **캡처하여 저장** 할 수 있습니다.


일급 객체란?

프로그래밍 언어에서 다음 3가지 조건을 모두 만족하는 대상을 말합니다.

1. 변수나 상수에 저장할 수 있음

2. 함수의 인자로 젼달할 수 있음

3. 함수의 반환값으로 사용할 수 있음

let closure = { () -> () in
	print("closure")
}
let closrue2 = closure // 변수나 상수에 저장할 수 있음
func doSomething(clousre: () -> ()) {
	closure()
}

doSomething(closure: { () -> () in // 함수의 인자로 젼달할 수 있음
	print("Hello")
})
func doSomething() -> () ->() {
	return { () -> () in // 함수의 반환값으로 사용할 수 있음
		print("Hello")
	}
}
let closure = doSomething()

closure()

🔹 클로저 문법 설명

{ (Parameters) -> Return Type in 
	//실행구문	
}
let closure = { (name: String) -> String in
	return "Hello, \(name)"
}

closure("Seonghye")

closure(name:"Seonghye") // Error

 

📌 설명

 - in 키워드 앞은 파라미터 / 리턴타입 작성
- in 뒤는 실제 실행될 코드를 작성
- 타입은 컴파일러가 추론 가능
- 실행 코드가 한 줄일 경우 return도 생략 가능


🔹 함수에 클로저 전달하기

func perform(action: () -> Void) {
	print("시작")
    action()
    print("끝")
}

perform {
	print("클로저 실행")
}

// 실행 결과
시작
클로저 실행
끝

📌 후행 클로저 문법

클로저가 마지막 인자일 경우, 함수 밖 중괄호로 작성 가능한 기능입니다.

단순한 문법적 편의 그 이상으로 가독성과 표현력을 높일 수 있어 사용하게 됩니다.

SwiftUI에서 사용되는 아래 코드도 내부적으로 후행 클로저 문법입니다.

즉 Swift의 코드 스타일과 DSL 구조에 최적화된 기능입니다.

VStack {
	Text("Hello World")
}

🔹 클로저의 캡처 기능

클로저는 자신이 생성될 때, 외부 스코프에 있는 변수나 상수를 "캡처"해서 나중에도 사용할 수 있도록 저장한다

즉, 클로저가 선언될 당시의 변수 상태를 기억하고 유지합니다.

이 변수는 클로저가 사라질 때까지 함께 살아있게 됩니다.

func makeCounter() -> () -> Int {
	var count = 0
    return {
    	count += 1
        return count
    }
}

let counter = makeCounter()
print(counter()) // 1
print(counter()) // 2

📘 해설

- count 는 makeCounter() 함수 안에 있는 지역 변수입니다.

- 하지만 return 한 클로저는 count를 캡처하게 됩니다.

- makeCounter()는 호출이 끝났지만, 클로저 덕분에 count는 계속 살아 있게 됩니다.

즉, 클로저가 count를 메모리에 유지하고 있기 때문에 가능한 코드입니다.

 

📌 포인트

- 클로저는 함수 외부의 변수를 캡처해서 계속 사용이 가능하다

- 변수의 값을 유지한다는 점에서 일반 함수와 다르다.

 

⚠️주의할 점!

클로저의 캡처 => 메모리 누수 가능

클로저가 self를 캡처할 경우, 강한 참조 순환(Retain Cycle)이 발생할 수 있습니다.

이 경우를 해결하기 위해 [weak self] or [unowned self]를 사용합니다.

class MyClass {
	var name = "Swift"
    
    func startTask {
    	someAsyncFunction {
        	print(self.name) // slef 캡처 -> 메모리 누수 위험
        }
    }
}

-------해결 방안
someAsyncFunction { [weak self] in // [weak self] or [unowned self]를 사용
	print(self?.name ?? "")
}

🔹 @escaping 클로저

func fetchData(completion: @escaping (String) -> Void) {
	DispatchQueue.global().async {
    	completion("데이터 도착")
    }
}

📌 설명

- 클로저가 함수 실행이 끝난 후에 실행해야하는 경우 @escaping  필요

- 보통 비동기 작업에 사용됨


🔹 고차함수에서의 클로저 활용

let numbers = [1,2,3,4,5]
let doubled = numbers.map {$0 * 2}
print(doubled)

 

📌 포인트

- map, filter, sorted 같은 함수는 내부에 클로저를 사용한다.

- Swift의 함수형 프로그래밍 문법에서의 핵심이다.


📘 마무리 요약
- 클로저는 이름 없는 함수
- in 을 기준으로 파라미터/ 코드 분리
- 외부 변수 캡처, @escaping, 메모리 관리까지 다양하게 사용됨

 

Comments