[내일배움캠프 Unity 9기]

05/14 TIL - 람다식 클로저, RigidBody 없는 Collider의 트리거

kyvv 2025. 5. 14. 20:39

RigidBody 없이Collider 끼리만 충돌하면 Trigger 이벤트는 발생하지 않는다. --> 유념할 것

 

람다식을 사용할 때는 클로저를 유의해야 한다
람다식에 들어가는 메서드가 매개변수를 갖고 있을 경우 이 매개변수는 힙에 복사되어 저장되는데 동일한 변수명을 여러 이벤트에 다르게 등록하고자 한다면 서로 다른 위치를 갖도록 해야한다(반복문으로 i를 넣는 것이 아니라 int num = i 같이 새로 선언한 변수를 사용해야 한다)

 

✅ 클로저(Closure)란?

클로저는 함수가 생성될 당시의 "외부 변수 환경"을 기억하여, 함수가 생성된 이후에도 그 변수들에 접근할 수 있게 하는 기능입니다.


🧠 쉽게 말해

람다(익명 함수)가 생성될 때 주변의 지역 변수들을 기억해서,
그 함수가 나중에 실행되더라도 변수가 살아있고 변할 수도 있게 해주는 거예요.


📌 예제 1: 간단한 클로저

csharp
복사편집
int counter = 0; Action a = () => Console.WriteLine(counter); counter = 5; a(); // 출력: 5
  • () => Console.WriteLine(counter)는 counter를 값이 아니라 변수 자체로 참조.
  • 그래서 counter = 5 이후에도 반영됨.
  • 람다 내부에 캡처된 counter는 스택이 아니라 힙에 복사된 변수로 동작.

📦 클로저의 동작 방식 (C# 기준)

  1. 람다 내에서 외부 변수를 참조하면
  2. 컴파일러는 해당 지역 변수들을 담을 익명 클래스를 생성
  3. 이 클래스의 필드로 외부 변수들을 복사함
  4. 람다는 그 클래스를 참조함으로써 변수에 계속 접근 가능

이게 바로 "환경(Env)"을 함께 묶어두는 함수 = Closure.


📌 예제 2: 반복문에서 클로저 문제

csharp
복사편집
for (int i = 0; i < 3; i++) { buttons[i].onClick.AddListener(() => Debug.Log(i)); }

이 경우 i는 같은 참조이기 때문에, 클릭 시점에는 항상 i == 3

해결법:

csharp
복사편집
for (int i = 0; i < 3; i++) { int index = i; buttons[i].onClick.AddListener(() => Debug.Log(index)); }
  • 여기서 index는 i의 복사본이라서,
  • 람다마다 서로 다른 값을 가짐 → 클로저 문제 해결!

📚 클로저는 어디에 유용한가?

상황설명
UI 버튼 클릭 이벤트 각 버튼마다 고유한 인덱스를 기억
코루틴에서 지역 변수 유지 람다나 익명 함수 내부에서 변수 유지
타이머/지연 처리 나중에 실행되더라도 당시 상태를 기억
DSL, 이벤트 시스템, 콜백 체인 상태가 캡처되어 있어야 올바르게 작동
 

🧩 클로저를 사용할 때 주의점

  • 클로저로 캡처한 변수는 수명이 늘어나기 때문에 메모리 누수의 원인이 될 수 있음
  • 특히 이벤트 등록 후 제거 안 할 경우, 캡처된 변수와 함께 계속 살아있게 됨
  • 람다 내에서 값이 변할 수도 있기 때문에, 값 복사 vs 참조 여부에 유의해야 함

✅ 요약

  • 클로저는 함수 + 외부 환경(변수) 를 하나의 단위로 캡슐화한 것
  • C#에서 클로저는 변수의 값이 아닌 참조를 기억함
  • 지역 변수라도 클로저에 의해 힙에 저장되어 수명이 늘어남
  • UI 이벤트, 코루틴, 딜레이 처리 등 유니티에서 매우 자주 등장