Swift Closure

Jee.e (황지희)·2022년 5월 9일
0
post-custom-banner

Capturing Values?

  • 클로저는 자신이 정의된 위치의 주변 문맥을 통해 상수나 변수를 캡처할 수 있다.
  • 쉽게 말해 값을 획득한것인데, 주변에 정의한 상•변수가 더 이상 존재하지 않더라도 해당 상•변수의 값을 자신 내부에서 참조하거나 수정하도록 저장하고 있다는 것이다.
  • original scope 에 존재하지 않아도 캡처할 수 있다.
  • 중첩 함수도 클로저인데, 자신을 포함하는 함수의 지역 변수나 지역 상수를 획득할 수 있다.

어떤 경우에 사용하나?

클로저는 비동기 작업에 많이 사용되는데,
클로저를 통해 비동기 Call-Back을 작성하는 경우 현재상태를 미리 획득해주지 않으면 실제로 클로저의 기능을 실행하는 순간에 메모리에 사용 할 상•변수가 존재하지 않는 경우가 발생한다.
이런경우에 사용하면 좋다.


✅ 사용 예시
중첩함수인 incrementer는 주변에 있는 runningTotal 과 매개변수인 amount 의 값을 캡처하고 있어서, runningTotalamountmakeIncrementer 함수의 실행이 끝나도 메모리에서 사라지지 않는다.

// 매개변수를 받지 않고 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 의 값을 계속 증가시킨다.
클로저를 상수나 변수에 할당할때는, 클로저의 값을 할당하는 것이 아니라 해당 클로저의 참조를 할당한다.
즉, 상수 incrementByTwomakeIncrementer 클로저를 참조하고 있어 출력값을 증가시킬 수 있는것이다.




탈출 클로저

  • 함수의 전달인자로 전달한 클로저가 함수 종료 후에 호출될 때, 클로저가 함수를 탈출(Escape)한다고 표현한다.
  • @escaping 키워드를 사용해 클로저가 탈출하는 것을 허용한다고 명시한다.
  • 비동기 작업으로 함수가 종료되고 난 후, 호출할 필요가 있는 클로저를 사용해야할때 탈출 클로저가 필요하다.

어떤 경우에 사용하나?

  • 클로저가 함수 외부에 정의된 변수나 상수에 저장되어, 함수가 종료된 후에 사용할 경우이다.
  • 비동기 작업을 실행하는 함수들은, 클로저를 Completion Handler 전달인자로 받아온다.
    즉, 함수가 작업을 끝낸(return) 이후에 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

profile
교훈없는 경험은 없다고 생각하는 2년차 iOS 개발자입니다.
post-custom-banner

1개의 댓글

comment-user-thumbnail
2022년 5월 9일

감사합니다 :)

답글 달기