1. Passing Parameters to Factories

  • 도입 배경
    • 몇몇 경우 get<T>() 를 사용할 때 값을 넘겨주어야 하는 경우가 존재.
    • get_it 에서는 2개 까지의 파라미터를 지원함.
      • 대부분의 경우를 2개까지의 파라미터 지원으로 해결가능함.
        • BuildContextdata object
        • service 설정 및 값
    • parametedynamic 으로 처리됨.
      • 그러나 runtime에서 타입을 확인하고, 맞지 않는 경우 오류를 야기한다.
void registerFactoryParam<T, P1, P2>(
  FactoryFuncParam<T, P1, P2> factoryFunc, {
  String? instanceName,
}) {}
void registerFactoryParamAsync<T, P1, P2>(
  FactoryFuncParamAsync<T, P1, P2> factoryFunc, {
  String? instanceName,
}) {}

사용 예시

2개의 Parameters

const userId = 'user-123';
const age = 25;
 
// Register factory accepting two parameters
getIt.registerFactoryParam<UserViewModel, String, int>(
  (userId, age) => UserViewModel(userId, age: age),
);
 
// Access with parameters
final vm = getIt<UserViewModel>(param1: userId, param2: age);

1개의 Parameter

  • 이 경우에는 2번째 인자 의 값을 void 를 넘겨주어야 함.
// Register with one parameter (second type is void)
getIt.registerFactoryParam<ReportGenerator, String, void>(
  (reportType, _) => ReportGenerator(reportType),
);
 
// Access with one parameter
final report = getIt<ReportGenerator>(param1: 'sales');

2. Registering Multiple Implementations

getIt 은 다양한 방법으로 동일한 타입의 다른 인스턴스를 등록하는 것을 지원함. 유용한 경우

  • 특정 타입의 모든 구현체를 검색해야 하는 경우
  • ex : plugin systems event hanlders modular architectures

Learn More

See the Multiple Registrations chapter for comprehensive documentation covering:

  • Different approaches to registering multiple instances
  • Why explicit enabling is required for unnamed registrations
  • How get<T>() vs getAll<T>() behave differently
  • Named vs unnamed registrations
  • Scope behavior with fromAllScopes
  • Real-world patterns (plugins, observers, middleware)

3. Managing Registrations

등록 여부 확인
  • 특정 타입이나 인스턴스가 이미 등록되었는지 테스트할 수 있습니다.
  • isRegistered<T> 함수를 통해 타입, 인스턴스 명칭, 특정 인스턴스 객체 자체로 등록 여부를 확인합니다.
bool isRegistered<T extends Object>({
  Object? instance,
  String? instanceName,
  Type? type,
});

Example:

// Check if type is registered
  if (getIt.isRegistered<ApiClient>()) {
    print('ApiClient is already registered');
  }
 
// Check by instance name
  if (getIt.isRegistered<Database>(instanceName: 'test-db')) {
    print('Test database is registered');
  }
 
// Check if specific instance is registered
  final myLogger = Logger();
  if (getIt.isRegistered<Logger>(instance: myLogger)) {
    print('This specific logger instance is registered');
  }

Unregistering Services (서비스 등록 해제)

  • 등록된 타입을 get_it에서 제거할 수 있으며, 선택적으로 폐기 함수(disposal function)를 호출할 수 있습니다.
FutureOr unregister<T extends Object>({
  Object? instance,
  String? instanceName,
  FutureOr Function(T)? disposingFunction,
  bool ignoreReferenceCount = false,
});

Example:

// Unregister by type with cleanup
  getIt.unregister<Database>(
    disposingFunction: (db) => db.close(),
  );
 
// Unregister by instance name
  getIt.unregister<ApiClient>(instanceName: 'legacy-api');
 
// Unregister specific instance
  final myService = getIt<MyService>();
  print('myService: $myService');
  getIt.unregister<MyService>(
    instance: myService,
    disposingFunction: (s) => s.dispose(),
  );
  • TIP: disposing function는 등록 시 제공했던 disposal function보다 우선하여 실행됩니다.

Resetting Lazy Singletons (레이지 싱글톤 리셋)

  • 등록을 해제하지 않고 레이지 싱글톤을 리셋(다음 접근 시 재생성 강제)하고 싶을 때 사용합니다.
  • resetLazySingleton<T>을 호출하면 다음 get() 호출 시 팩토리 함수가 다시 실행됩니다.
  • 사용 시점: 캐시 데이터 갱신(로그인/로그아웃 후), 테스트 간 상태 리셋, 설정 재로드 시.
    FutureOr resetLazySingleton<T extends Object>({
    T? instance,
    String? instanceName,
    FutureOr Function(T)? disposingFunction,

});


**Example**

```dart
// Reset so it recreates on next get()
getIt.resetLazySingleton<UserCache>();

// Next access will call the factory function again
final cache = getIt<UserCache>();
print('cache: $cache'); // New instance created

If you need to reset all lazy singletons at once (instead of one at a time), use resetLazySingletons() which supports scope control and batch operations. See resetLazySingletons() documentation for details.

Reset All Lazy Singletons (모든 레이지 싱글톤 리셋)

  • 하나씩 리셋하는 대신 모든 레이지 싱글톤을 한꺼번에 리셋하려면 resetLazySingletons()를 사용합니다.
  • 스코프 제어 및 일괄 작업을 지원합니다.
    Future<void> reset({bool dispose = true});

Resetting All Registrations (모든 등록 리셋)

  • 등록된 모든 타입을 삭제합니다(테스트 또는 앱 종료 시 유용)

  • Future<void> reset({bool dispose = true});
    // Reset everything and call disposal functions
    await getIt.reset();

// Reset without calling disposals await getIt.reset(dispose: false); ```

  • await getIt.reset()은 비동기적으로 동작하며, 등록된 역순(마지막 등록된 것이 먼저 폐기)으로 정리됩니다. Use cases:

  • Between unit tests (tearDown or tearDownAll)

  • Before app shutdown

  • Switching environments entirely

Overwriting Registrations (등록 덮어쓰기)

  • 기본적으로 get_it은 버그 방지를 위해 동일 타입을 두 번 등록하는 것을 막습니다. 덮어쓰기를 허용하려면 getIt.allowReassignment = true;를 설정해야 합니다.
    getIt.allowReassignment = true;

// Now you can re-register getIt.registerSingleton(ConsoleLogger()); getIt.registerSingleton(FileLogger()); // Overwrites previous ```

  • 주의: 재할당 허용은 버그를 찾기 어렵게 만듭니다. 임시 오버라이드가 필요한 경우(특히 테스트)에는 scopes  사용을 권장합니다.

Skip Double Registration (중복 등록 건너뛰기 - 테스트 전용)

  • 테스트 시 중복 등록에 대해 에러를 던지는 대신 조용히 무시하려면 getIt.skipDoubleRegistration = true;를 설정합니다. 이미 등록된 경우 아무 작업도 하지 않습니다.
      getIt.skipDoubleRegistration = true;

// If already registered, this does nothing instead of throwing getIt.registerSingleton(Logger());



----
## RAW
흥미롭네요. "약한 참조(Weak Reference)"와 "캐싱"이라는 단어를 섞으니 제법 그럴듯한 최적화 기법처럼 들리나 보죠?

그런데 말입니다. 본인이 방금 설명한 그 '캐시된 팩토리'의 메커니즘을 제대로 들여다보긴 한 건가요? **가비지 컬렉션(Garbage Collection)**의 동작 방식과 연결 지어 생각해 보시죠.

1. **예측 불가능성**: 약한 참조로 캐싱된 인스턴스가 언제 해제될지는 전적으로 GC의 판단에 달려 있습니다. 그렇다면, 개발자가 제어할 수 없는 **비결정적인(Non-deterministic) 생명주기**를 가진 객체를 '최적화'의 수단으로 삼는 것이 아키텍처 관점에서 과연 안전하다고 생각하십니까?
  
2. **메모리 스래싱(Memory Thrashing)**: 만약 해당 객체에 대한 외부 참조가 끊기자마자 GC가 작동하고, 직후에 다시 팩토리가 호출된다면 어떻게 될까요? 인스턴스를 계속해서 생성하고 파괴하는 과정이 반복될 텐데, 이게 과연 **싱글톤**이나 **일반 팩토리**보다 성능상 이점이 있다고 단언할 수 있나요?
  

단순히 "메모리에 남아 있으면 재사용한다"는 문장에 매몰되지 마십시오. **Dart**의 GC가 **Young Generation**과 **Old Generation**을 어떻게 관리하는지 알고 있다면, 약한 참조를 활용한 캐싱이 실제로 성능에 기여하는 '임계점'이 어디인지 설명할 수 있어야 합니다.

캐시된 팩토리가 일반적인 팩토리보다 무조건 우월한 방식인가요? 아니면 특정 상황에서만 유효한 고육지책인가요? 그 '특정 상황'이 무엇인지 논리적으로 증명해 보시죠. 그게 최선입니까? 다시 생각해보시죠.



라티, 지적하신 부분은 시스템 아키텍처와 메모리 관리의 핵심을 찌르는 날카로운 분석입니다. '약한 참조(Weak Reference)'를 활용한 캐싱은 결코 만능 해결책이 아니며, 잘못 사용하면 오히려 독이 될 수 있는 **조건부 최적화 기법**입니다.

제시하신 의문에 대해 냉철하게 분석한 결과는 다음과 같습니다.

### 1. 비결정적 생명주기: 아키텍처의 불안정성

약한 참조 캐싱은 객체의 생명주기를 개발자가 아닌 **GC의 불규칙한 스케줄링**에 위임합니다. 이는 상태를 가진(Stateful) 객체에는 치명적입니다.

- **리스크:** 객체가 메모리에서 해제되었다가 다시 생성될 때, 이전의 내부 상태가 초기화되면서 로직의 일관성이 깨질 수 있습니다.
  
- **결론:** 따라서 이 기법은 **불변(Immutable) 객체**나 **상태가 없는(Stateless) 서비스**에만 제한적으로 사용해야 안전합니다. 아키텍처 관점에서 '안전한 최적화'라고 부르기엔 제어권 상실이라는 대가가 큽니다.
  

### 2. 메모리 스래싱(Memory Thrashing)과 오버헤드

말씀하신 대로 참조 해제와 팩토리 호출이 빈번하게 교차하면 성능은 최악으로 치닫습니다.

- **역효과:** 일반 팩토리는 단순 생성만 하면 되지만, 캐시된 팩토리는 `WeakReference`를 확인하고, 캐시 맵을 조회하고, 재생성 후 다시 등록하는 부가적인 연산이 추가됩니다.
  
- **임계점:** Dart의 Scavenger(Young Generation GC)는 매우 빈번하게 발생합니다. 객체가 소멸과 생성을 반복하며 **Old Generation으로 승격되지 못하고 계속 Young 영역에서 생성 비용을 발생시킨다면**, 이는 싱글톤보다 못할 뿐더러 일반 팩토리보다도 느려집니다.
  

### 3. 논리적 증명: '특정 상황'은 언제인가?

캐시된 팩토리는 일반 팩토리보다 우월하지 않습니다. 오직 다음의 **좁은 임계점**을 만족할 때만 유효한 '고육지책'입니다.

|**조건**|**상세 내용**|
|---|---|
|**객체의 무거움**|생성 비용이 극도로 비싸서(예: 대규모 설정 파싱) 가급적 재사용해야 함.|
|**비연속적 참조**|앱의 여러 화면에서 간헐적으로 해당 객체를 참조하지만, 동시에 모든 곳에서 참조하지는 않음.|
|**메모리 압박**|싱글톤으로 상주시키기엔 메모리 점유가 부담스러워, 시스템 메모리가 부족할 때 최우선으로 해제되어야 함.|

### 4. 결론: 최선인가?

아니요, **최선이 아닙니다.** 대부분의 비즈니스 로직에서는 예측 가능한 **싱글톤(Singleton)**이나, 필요할 때마다 생성하는 **일반 팩토리(Factory)**가 훨씬 견고합니다. 약한 참조 캐싱은 **'메모리 부족 시 자진 반납'과 '사용 중일 때 재사용'이라는 두 마리 토끼를 잡으려는 타협점**일 뿐입니다.

Dart 환경이라면 차라리 `lru_cache`처럼 명시적으로 크기를 제어하는 캐싱 전략이 훨씬 결정적이고 안전한 선택입니다. "메모리에 남아 있으면 재사용한다"는 감성적인 접근보다는, 시스템의 부하를 정량적으로 계산한 설계를 우선해야 합니다.