다음 글 : 2. Object Registration - Deeping

 

getIt은 아래 기준에 따라 상이한 등록 방식 제공

  • 언제 생성될 지
  • 얼마나 오래 객체가 지속할 지
Quick Reference
TypeWhen CreatedHow Many InstancesLifetimeBest For
SingletonImmediatelyOnePermanentFast to create, needed at startup
LazySingletonFirst accessOnePermanentExpensive to create, not always needed
FactoryEvery get()ManyPer requestTemporary objects, new state each time
Cached FactoryFirst access + after GCReused while in memoryUntil garbage collectedPerformance optimization

1. SingleTon

추천 케이스

  • 생성 비용이 매우 낮아 앱의 초기 렌더링을 지연시키지 않는 객체
  • 앱 구동 시 즉각적으로 필요한 전역 상태 혹은 설정 정보를 다루는 객체
T registerSingleton<T extends Object>(
  T instance, {
  String? instanceName,
  bool? signalsReady,
  DisposingFunc<T>? dispose,
});
  • 인스턴스의 경우, 등록 즉시 생성.
  • get<T>() 을 통해 T 를 제공하면 해당 값을 받을 수 있음.

Parameters 필수

  • instance : 등록할 인스턴스 선택
  • instanceName : 같은 타입의 인스턴스를 여러 개 등록할 경우, 이름을 다르게 하여 구분할 때 사용.
  • signalReady : true 설정 시 해당 인스턴스가 준비가 되면 항상 신호를 제공
    • (async 를 이용한 초기화 시 주로 사용)
  • disponse : 등록 해제 혹은 재설정 시 사용할 cleanUp 함수

사용 예시

void configureDependencies() {
  // Simple registration
  getIt.registerSingleton<Logger>(Logger());
 
  // With disposal
  getIt.registerSingleton<Database>(
    Database(),
    dispose: (db) => db.close(),
  );
}

2. LazySingleton

적합한 사용 예시

  1. 생성 비용이 비싼 객체 : database, Http Client
  2. 모든 User에게 항상 필요한 객체가 아닌 경우.
  3. 지연 초기화를 하고 싶은 경우
void registerLazySingleton<T>(
  FactoryFunc<T> factoryFunc, {
  String? instanceName,
  DisposingFunc<T>? dispose,
  void Function(T instance)? onCreated,
  bool useWeakReference = false,
}) {}
  • registerLazySingleton 을 사용할 때, T 라는 원하는 인스턴스 타입을 반환하는 팩토리 함수 제공.
  • 해당 함수는 get<T>() 에 최초 접근 시에 호출
    • 이후에는 항상 동일한 인스턴스 제공

Parameters factoryFunc : 인스턴스를 생성하는 함수 선택 instanceName , dispose SingleTon 에서와 동일 onCreated : 인스턴스가 생성된 이후의 콜백 함수 useWeakReference : true 설정 시 사용하지 않으면 GC 의 대상이 되어 정리된다.

**예시

void configureDependencies() {
  // Simple registration
  getIt.registerLazySingleton<ApiClient>(() => ApiClient());
 
  // With disposal and onCreated callback
  getIt.registerLazySingleton<Database>(
    () => Database(),
    dispose: (db) => db.close(),
    onCreated: (db) => print('Database initialized'),
  );
}
 
// First access - factory function runs NOW
final api = getIt<ApiClient>(); // ApiClient() constructor called
 
// Subsequent calls - returns existing instance
final sameApi = getIt<ApiClient>(); // Same instance, no constructor call

3. Factory

적합한 사용 예시

  • 일시적으로 사용하는 객체 dialog, forms, 임시적인 데이터 보관 역할 등
  • 매번 초기값이어야 하는 객체
  • 짧은 생명 주기를 가진 객체

생성 비용이 비싼 경우 cached factory 의 사용을 검토

void registerFactory<T>(
  FactoryFunc<T> factoryFunc, {
  String? instanceName,
}) {}
  • 등록 시, 매번 새로운 T 라는 인스턴스를 생성하는 메서드를 제공하여야 함.
    • SingleTon 에서와 달리 매번 호출 마다 새로운 객체를 획득함.

Parameters SingleTon 에서와 동일

**예시

void configureDependencies() {
  getIt.registerFactory<ShoppingCart>(() => ShoppingCart());
}

4. Cached Factories

  • 일반 팩토리와 싱글톤 사이의 성능 최적화 방식
    • 첫 번째 호출 시 : 새로운 인스턴스 생성
      • 이를 weak reference 로 캐싱
    • 이후 해당 인스턴스가 메모리에 남아 있는 경우, 캐싱된 인스턴스를 사용
//type-a
void registerCachedFactory<T>(
  FactoryFunc<T> factoryFunc, {
  String? instanceName,
});
 
//type-b
void registerCachedFactoryParam<T, P1, P2>(
  FactoryFuncParam<T, P1, P2> factoryFunc, {
  String? instanceName,
});
 
//type-c
void registerCachedFactoryAsync<T>(
  FactoryFuncAsync<T> factoryFunc, {
  String? instanceName,
});
 
//type-d
void registerCachedFactoryParamAsync<T, P1, P2>(
  FactoryFuncParamAsync<T, P1, P2> factoryFunc, {
  String? instanceName,
});
  • Type-a : `registerCachedFactory
    • 매개변수 없이 객체 생성
  • Type-b : registerCachedFactoryParam<T, P1, P2>
    • 두 개의 매개변수(P1, P2) 를 전달받아 T 타입의 인스턴스 생성
    • 객채 생성 시점에 동적인 데이터가 필요할 때 사용
  • Type-c : registerCachedFactoryAsync<T>
    • 비동기적으로 T 타입의 인스턴스를 생성.
    • Future<T> 를 반환하는 생성 로직에 사용
  • Type-d : registerCachedFactoryParamAsync<T, P1, P2>
    • 두 개의 매개변수를 전달받아 비동기적으로 인스턴스를 생성.
    • 매개변수가 필요한 복잡한 비동기 초기화 로직을 수행 시 사용.

동작

  1. 최초 호출 : factory 처럼 새로운 인스턴스 생성
  2. 만약 캐시된 인스턴스가 존재하면 다른 호출에서는 캐시된 인스턴스 반환 싱글톤
  3. 만약 Garbage Collected 된다면 새로운 객체 생성

사용 예제

// Register a cached factory
  getIt.registerCachedFactory<HeavyParser>(() => HeavyParser());
 
// First call - creates instance
  final parser1 = getIt<HeavyParser>();
  print('parser1: $parser1'); // New instance created
 
// Second call - reuses if not garbage collected
  final parser2 = getIt<HeavyParser>();
  print('parser2: $parser2'); // Same instance (if still in memory)
 
// After garbage collection (no references held)
  final parser3 = getIt<HeavyParser>();
  print('parser3: $parser3'); // New instance created

With parameters:

getIt.registerCachedFactoryParam<ImageProcessor, int, int>(
  (width, height) => ImageProcessor(width, height),
);
 
// Creates new instance
final processor1 = getIt<ImageProcessor>(param1: 1920, param2: 1080);
print('processor1: $processor1');
 
// Reuses same instance (same parameters)
final processor2 = getIt<ImageProcessor>(param1: 1920, param2: 1080);
print('processor2: $processor2');
 
// Creates NEW instance (different parameters)
final processor3 = getIt<ImageProcessor>(param1: 3840, param2: 2160);
print('processor3: $processor3');

좋은 사용 사례:

  • 빈번하게 재생성되는 무거운 객체: 파서(Parser), 포맷터(Formatter), 계산기(Calculator)
  • 메모리에 민감한 시나리오: 자동 정리를 원하면서도 가급적 재사용을 선호하는 경우
  • 초기화 비용이 비싼 객체: 데이터베이스 연결, 파일 리더
  • 수명이 짧거나 중간 정도인 객체: 일정 기간 동안만 활성 상태로 유지되고 영구적이지 않은 경우

다음의 경우 사용 금지:

  • 객체가 항상 새로 생성되어야 할 때: 일반 팩토리 사용
  • 객체가 영구적으로 유지되어야 할 때: 싱글톤 또는 레이지 싱글톤 사용
  • 재사용해서는 안 되는 중요한 상태를 객체가 보유하고 있을 때

Performance characteristics:

TypeCreation CostMemoryReuse
FactoryEvery callLow (immediate GC)Never
Cached FactoryFirst call + after GCMedium (weak ref)While in memory
Lazy SingletonFirst call onlyHigh (permanent)Always

Comparison example:

// Factory - always new, immediate cleanup
getIt.registerFactory<JsonParser>(() => JsonParser());
final p1 = getIt<JsonParser>();
print('p1: $p1'); // Creates instance 1
final p2 = getIt<JsonParser>();
print('p2: $p2'); // Creates instance 2 (different)
 
// Cached Factory - reuses if possible
getIt.registerCachedFactory<JsonParser>(() => JsonParser());
final p3 = getIt<JsonParser>();
print('p3: $p3'); // Creates instance 3
final p4 = getIt<JsonParser>();
print('p4: $p4'); // Returns instance 3 (if not GC'd)
 
// Lazy Singleton - reuses forever
getIt.registerLazySingleton<JsonParser>(() => JsonParser());
final p5 = getIt<JsonParser>();
print('p5: $p5'); // Creates instance 4
final p6 = getIt<JsonParser>();
print('p6: $p6'); // Returns instance 4 (always)

Note

어떤 타입으로 GetIt 에 인스턴스를 등록해야할까?

구체적인 타입, 인터페이스(추상 인터페이스) 모두 가능.

여러 구현체를 사용하는 경우 인터페이스 그 외의 경우에는 구체적인 클래스를 직접 등록하는 것이 좋다. 코드의 단순성, IDE 탐색의 용이성 증가