- 클로저는 자신이 정의된 위치의 주변 문맥을 통해 상수나 변수를 캡처할 수 있다.
- 쉽게 말해 값을 획득한것인데, 주변에 정의한 상•변수가 더 이상 존재하지 않더라도 해당 상•변수의 값을 자신 내부에서 참조하거나 수정하도록 저장하고 있다는 것이다.
original scope
에 존재하지 않아도 캡처할 수 있다.- 중첩 함수도 클로저인데, 자신을 포함하는 함수의 지역 변수나 지역 상수를 획득할 수 있다.
클로저는 비동기 작업에 많이 사용되는데,
클로저를 통해 비동기 Call-Back을 작성하는 경우 현재상태를 미리 획득해주지 않으면 실제로 클로저의 기능을 실행하는 순간에 메모리에 사용 할 상•변수가 존재하지 않는 경우가 발생한다.
이런경우에 사용하면 좋다.
✅ 사용 예시
중첩함수인 incrementer는 주변에 있는 runningTotal
과 매개변수인 amount
의 값을 캡처하고 있어서, runningTotal
과 amount
는 makeIncrementer
함수의 실행이 끝나도 메모리에서 사라지지 않는다.
// 매개변수를 받지 않고 Int를 반환하는 함수를 반환하는 makeIncrementer 함수
func makeIncrementer(forIncrementer amount: Int) -> (() -> Int) {
var runningTotal = 0
func incrementer() -> Int {
runningTotal += amount
return runningTotal
}
return incrementer
}
let incrementByTwo: (() -> Int) = makeIncrementer(forIncrementer: 2)
let first = incrementByTwo() //2
let second = incrementByTwo() //4
참조 타입이란 객체의 주소를 메모리에 저장해, 사용시엔 그
주소
를 참조하는 것이다.
때문에 동적 할당 메모리 영역인Heap
에 저장된다.
해당 주소 값이 사라지는 시점에 메모리 해제를 잘 해줘야 메모리 릭이 발생하지 않는다.
앞서 예시 코드를 보면, incrementByTwo
는 상수이다.
이때, 상수 클로저는 Capturing Values
를 통해 변수 runningTotal
의 값을 계속 증가시킨다.
클로저를 상수나 변수에 할당할때는, 클로저의 값을 할당하는 것이 아니라 해당 클로저의 참조를 할당한다.
즉, 상수 incrementByTwo
는 makeIncrementer
클로저를 참조하고 있어 출력값을 증가시킬 수 있는것이다.
@escaping
키워드를 사용해 클로저가 탈출하는 것을 허용한다고 명시한다.Completion Handler
전달인자로 받아온다.Completion Handler
라는 클로저를 호출하기 때문에 클로저는 함수를 탈출해 있어야 하는것이다.✅ 사용 예시
task.resume()
이 실행되고 난 이후에,completionHandler
가 동작한다.
func dataTask(request: URLRequest, completionHandler: @escaping (NetworkResult) -> Void) {
let task = session.dataTask(with: request) { data, response, error in
if let error = error {
return completionHandler(NetworkResult(.unknownError))
}
guard let httpResponse = response as? HTTPURLResponse else {
return completionHandler(NetworkResult(.unknownError))
}
if (200..<300).contains(httpResponse.statusCode),
let data = data {
return completionHandler(NetworkResult(data))
}
return completionHandler(NetworkResult(.statusCodeError))
}
task.resume()
}
아래 예시에서 [weak self]
가 빠지면 누수 발생
class ClosureTestController: UIViewController {
deinit { fatalError("ClosureTestController 메모리 해제") }
var testClosure: ((Data?, Error?) -> ())?
override func viewDidLoad() {
super.viewDidLoad()
Service.shared.fetchData { [weak self] (err) in
if let err = err { return }
self?.showAlert()
}
}
func showAlert() {
let alertController = UIAlertController(title: "5/9", message: "클로저 학습", preferredStyle: .alert)
present(alertController, animated: true, completion: nil)
}
}
참고문서
1. https://velog.io/@kipsong/Swift%EB%AC%B8%EB%B2%95-Closure-Capture%EC%97%90-%EB%8C%80%ED%95%98%EC%97%AC
2. https://lsh424.tistory.com/77
감사합니다 :)