[Swift] escaping closure

·2024년 6월 18일
0

Swift 문법

목록 보기
3/16

escaping closure

  • 인수로 전달된 클로저가 함수가 종료된 후 실행되는 클로저
  • 탈출을 허락한다는 의미로 파라미터 타입 앞에 @escaping 키워드 작성

practice

var closures: [() -> Void] = []

func addClosure(closure: @escaping () -> Void) {
    closures.append(closure)
}

클로저를 저장할 배열 closures ,
closures 에 클로저를 추가할 메서드 addClosure 를 구현했다.
addClosure 을 통해 전달된 인수 클로저는 closures 에 추가만 되고 아직 실행되지는 않는다.


addClosure { print("escaping closure1... 🐌") }
addClosure { print("escaping closure2... 🐌") }

closures.forEach { $0() } // 클로저 실행

addClosure 이 종료된 후 클로저를 실행할 경우에
추가된 클로저가 실행될 수 있도록 전달되는 인수는 escaping 클로저로 선언되어야 한다.

즉, 함수가 종료된 후에도 해당 클로저를 사용할 수 있게 하기 위해 escaping 클로저로 작성해야 한다.



@escaping 으로 작성하지 않으면 아래의 오류가 발생한다.

Converting non-escaping parameter 'closure' to generic parameter 'Element' may allow it to escape

해당 오류는 escaping 클로저가 필요한 곳에 non-escaping 클로저로 작성했다는 의미이다.
이처럼 escaping 클로저는 함수가 종료된 후에 클로저가 실행되어야 하는 경우에 사용할 수 있다.
대다수가 비동기 로직과 관련있다.



escaping 클로저의 self 참조

var closures: [() -> Void] = []

func doSomethingWithEscapingClosure(closure: @escaping () -> Void) {
    closures.append(closure)
}

func doSomethingWithClosure(closure: () -> Void) {
    closure()
}

doSomethingWithEscapingClosure

  • 클로저 배열에 클로저를 추가하는 함수 (escaping 선언 필요)

doSomethingWithClosure

  • non-escaping 클로저를 호출하는 함수

class TestClass {
    var x = 10

    func testMethod() {
        doSomethingWithClosure { x = 100 }
    }
}

TestClass 를 만들고 testMethod 를 구현해 보자.
doSomethingWithClosure 을 호출하고 x를 100으로 변경해 보았다.
self를 암시적으로 참조할 수 있다.


    func testMethod() {
//        doSomethingWithClosure { x = 100 }
        doSomethingWithEscapingClosure { x = 200 } // error! 🚨
    }

똑같이 doSomethingWithEscapingClosure 을 호출해 보자.
이번엔 self 를 명시적으로 참조해야 한다는 에러가 발생한다.


escaping 클로저는 함수가 종료된 이후에도 실행될 수 있다.
즉, escaping 클로저가 캡처한 self가 이미 메모리에서 해제되었을 가능성이 있다.
따라서 명시적으로 self 를 작성함을 통해 순환 참조의 가능성을 인지할 수 있도록 한다.


class TestClass {
    var x = 10

    func testMethod() {
        doSomethingWithClosure { x = 100 }
        doSomethingWithEscapingClosure { self.x = 200 }
    }
}

let tc = TestClass()
tc.testMethod()
print(tc.x) // 100

closures.first?()
print(tc.x) // 200

인스턴스를 생성하고 각각 x를 호출해 보면 다음과 같이 출력된다.


doSomethingWithEscapingClosure { [weak self] in
		self?.x = 200
}

물론 escaping 클로저일 때는 위와 같이 weak self로 참조하는 것이 가장 안전하다.
cf. non-escaping 클로저는 함수가 종료되면 해당 클로저는 더이상 사용되지 않는 것이 명확하기 때문에 메모리가 해제되어 순환 참조가 발생하지 않는다.





정리 - escaping 클로저란

인수로 전달된 클로저가 함수가 종료된 후에 실행될 때는, escaping 클로저로 작성해 주어야 한다.

0개의 댓글

관련 채용 정보