클로저 심화

JG Ahn·2024년 11월 5일

swift 심화

목록 보기
4/18
post-thumbnail

Escaping Closure

- @Escaping 란?

함수가 종료된 후에도 클로저를 사용할 때 쓰는 키워드

  • 기본적으로 함수의 파라미터로 전달된 클로저는 함수 내부에서만 사용할 수 있지만, 함수가 종료된 후에도 클로저를 사용할 경우 @escaping 키워드 사용
  • 클로저는 기본값이 non-escaping
    • 파라미터로 받은 클로저는 다른 변수나 상수에 할당 불가
    • 파라미터로 받은 클로저는 함수의 실행이 완료되기 전에 함수 내부에서 클로저를 사용해야 함
// 정상적인 클로저 
func testEsacpingClosure(closure: () -> Void) {
    closure()
}

testEsacpingClosure {
    print("Hello")
}
// 파라미터로 받은 클로저가 함수가 실행된 후 사용되는 경우
import UIKit
func testEsacpingClosure(closure: () -> Void) {
    // 1초 뒤에 closure를 호출해달라는 의미
    DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
        closure() // Error 발생! 
    }
}

// 함수가 끝나고 1초 뒤에 closure를 호출하기 때문에 오류 발생
//파라미터로 받은 클로저는 다른 변수나 상수에 할당 불가
func testEsacpingClosure(closure: () -> Void) {
    let newClosure: () -> Void = closure // Error 발생
}
  • @escaping closure는 주로 비동기적인 작업 후 클로저 호출시 사용
    • 예시 : 서버에서 API 호출시 서버의 응답을 기다린 후 데이터를 받아 처리하는데, 이때 서버 응답이 오기 전에 함수가 먼저 종료될 수 있음. 따라서 @escaping을 사용해 서버 응답을 받은 후 클로저 호출.
// 사용 방법
import UIKit
func testEsacpingClosure(closure: @escaping () -> Void) {
    
    // 1초 뒤에 코드블록을 실행하는 코드
    DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
        closure() // 오류발생 ❌
    }
}

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

클로저 캡처 (closure capture)

- 클로저 캡처란?

클로저가 컨텍스트(자신이 생성된 코드블럭) 내에 있는 변수, 상수에 접근하는 것

  • 클로저는 참조 형식으로 값을 사용. 즉, 값이 변경되면 캡처된 값도 변경된 값 출력
  • 클로저가 선언된 뒤 선언된 변수, 상수는 클로저로 사용할 수 없음
struct Person {
    var name: String
    var age: Int
}

func testClosureCapture() {
    var person = Person(name: "Brody", age: 20)
    
    let closure = {
        // 해당 클로저에서 person을 캡처해 사용
        // value type이기 때문에 나이 20을 복사하여 사용하여 출력했을 때
        // 값이 20,20 이 나오지 않는 이유는 기본적으로 클로저캡처는 "참조형식"으로 하기 때문
        print(person.age)
    }
    closure() // 출력 값 : 20
	
    person.age = 25

    closure() // 출력 값 : 25
}

testClosureCapture()
// 클로저 캡처는 클로저 사용 전에 선언된 변수나 상수만 가능합니다.

func testClosureCaptureError() {
    var a = 1
    
    let closure = {
        print(a)
        print(b) // Error 발생! 클로저 생성된 후의 b를 캡처할 순 없음
    }
    var b = 1
}

캡처 리스트

- 캡처리스트란?

클로저가 주변 환경의 변수를 캡처할 때 메모리 관리를 명시적으로 제어할 수 있는 방법

  • 클로저는 Value Type의 값도 참조로 캡처하지만 Value Type으로 참조할 수 있음
  • reference type 은 참조방식을 정할 수 있음
  • [ ] 대괄호 안에 사용할 변수나 상수를 작성하여 캡처리스트를 정할 수 있다

Reference Type 참조 방식 정하기

  • 기본은 strong이기 때문에 강한순환참조가 발생할 수 있음
  • 대괄호를 사용하고 참조방식과 캡처할 변수(상수) 이름을 작성
  • weak, unowned 사용 가능
class Animal {
    var name: String
    var age: Int

    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
}

func testClosureCaptureReference() {
    var animal = Animal(name: "Dog", age: 1)
    
    let closure = { [weak animal] in // [ ]
        print(animal?.age)
    }
    
    closure() // 출력 값 : Optional(1)
    animal.age = 2
    closure() // 출력 값 : Optional(2)
}


testClosureCaptureReference()

Value Type 으로 캡처하는 방법

  • 대괄호를 사용하고 캡처할 변수(상수)를 작성
struct Person {
    var name: String
    var age: Int
}

func testClosureCapture() {
    var person = Person(name: "Brody", age: 20)
    var a = 1
    
    let closure = { [person] in
        print(person.age)
        print(a)
    }
    closure() // 출력 값 : 20
    person.age = 25
    a = 2
    closure() // 출력 값 : 20
}

testClosureCapture()

/* 출력 값
20 // person은 value type으로 복사를 하였기 때문에 변경이 없음
1 // a는 참조캡처를 하여 값 변경
20 // person은 value type으로 복사를 하였기 때문에 변경이 없음
2
*/

0개의 댓글