2025. 12. 10. 21:06ㆍ본 캠프
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
public static class EventBus
{
//구독자를 타입형태로 리스트로 관리
private static readonly Dictionary<Type, IList> _subscribers = new();
//IList에 리스트로 관리할 구독자들을 불러오기
private static List<WeakReference<Action<T>>> GetList<T>()
{
var type = typeof(T);
// 딕셔너리에 해당 이벤트 타입이 없다면 새 리스트를 생성하여 등록
if (!_subscribers.TryGetValue(type, out var list))
{
list = new List<WeakReference<Action<T>>>();
_subscribers[type] = list;
}
//IList(콜백 저장된 리스트)를 실제 이벤트 형태의 타입으로 캐스팅해서 반환
return (List<WeakReference<Action<T>>>)list;
}
|
cs |
이벤트버스라는 라우터를 활용하기 위해서는 어떻게 구성해야 할까?
구독자들을 목록으로 관리해야 할 거고 이벤트도 타입단위로 분리해서
해당 이벤트 타입에 맞는 구독자를 불러와야 하는 형태로 코드를 작성해야 한다.
IList는 C# System에서 제공하는 비제네릭 리스트 인터페이스다.
어떤 리스트든 담을 수 있는 인터페이스인 IList를 선언하고 실제로 넣을 때는 List<WeakReference<Action<T>>>를 넣는다.
꺼낼 때는 타입에 맞춰 캐스팅해서 사용하면 된다.
그렇다면 WeakReference는 뭘까?
Unity에서 로직 흐름상 가장 흔하게 일어나는 오류 패턴 중
-씬이 언로드 되거나/오브젝트가 Destroy 됐을 때
정적(static) 객체가 오브젝트를 여전히 참고하고 있어서 메모리에서 사라지지 않는 문제가 있다.
이걸 Strong reference라고 칭하는데
이벤트버스가 구독자를 붙잡고 놓지 않은 상황이라면 플레이신을 재진입하거나 오브젝트가 파괴될 때 오류가 생길 수 밖에 없다.

Weak reference는 참조는 하지만 객체의 생명주기를 책임지지 않는다.
Eventbus가 구독자를 갖고 있어도 구독자가 Destroy 되면 GC가 지울 수 있다.
GC가 객체를 지우면 WeakReference 내부 target이 null이 되면서 Eventbus는 이를 감지하고 자동으로 제거할 수 있게 된다.
Additive Scene형식으로 UI를 메인신과 분리해서 구현이 목표이기 때문에 이와같은 '약한'참조의 형태가 필수적이게 된다.
그렇기에 List<WeakReference>를 사용하게 되면 하나의 이벤트 타입에 구독자가 여러 명 존재하는 경우
ex: 스트레스 변경시 -> HUDManager에서는 이미지 fillbar 변경/ UIManager에서는 해당수치에 영향을 받는 UI 변경 등
여러 WeakReference를 저장해야 하기때문에 List가 된다.
그럼 여기서 의문이 생기는데 Dictionary<Type, List<WeakReference<Action<T>>>> 를 쓰면 되지않을까? 라는 생각이 들지만
T가 이벤트마다 다르기 때문에 제네릭 타입이 이벤트마다 달라서 T가 다른 List<T>들을 공통 타입으로 관리해야 한다.
그래서 상위 타입인 IList 인터페이스를 사용한다.
Dictionary는 구조상 하나의 타입이어야 하기 때문에 비제네릭 상위 타입인 IList로 선언 한뒤
-> 제네릭 으로 진입 해서 실제 콜백들을 캐스팅해서 쓰는 방식이 된다.
이벤트버스의 구조도

|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
public static void Publish<T>(T eventData)
{
var list = GetList<T>();
var snapshot = list.ToArray(); // 리스트 변경 후 오류방어를 위한 복사본 생성
foreach (var weak in snapshot)
{
// Destroy된 객체 Traget null -> 자동 제거 (신 변경)
if (!weak.TryGetTarget(out var callback))
{
list.Remove(weak);
continue;
}
// 예외처리(개별 구독자 오류로 전체 Publish가 중단되지 않도록 보호처리)
try
{
callback.Invoke(eventData);
}
catch (Exception ex)
{
Debug.LogError($"[EventBus] {typeof(T).Name} 처리 중 예외: {ex}");
}
}
}
|
cs |
발행시에는 try-catch 구조를 통해 구독자 한쪽에서 오류가 나와도 아예 멈춰버리는 상황을 방지해야 한다.
'본 캠프' 카테고리의 다른 글
| [내일배움캠프 본 캠프 54일차] 사물을 확대해서 돌려보기_1 (0) | 2025.12.12 |
|---|---|
| [내일배움캠프 본 캠프 53일차] 볼륨/비네팅 DOTween (0) | 2025.12.11 |
| [내일배움캠프 본 캠프 51일차] 이벤트버스에 대한 이해 (0) | 2025.12.09 |
| [내일배움캠프 본 캠프 50일차] R&D 기간 (0) | 2025.12.08 |
| [내일배움캠프 본 캠프 49일차] 팀프로젝트 5일차 (0) | 2025.12.05 |