剖析Flutter的常用库get_it
前言
通常外企的领导在工作群发通知消息
就马上看到有人回复:「I get it.」
这时候,也会有人说:「I got it.」
只是当你说 I get it. 的时候,更像是明白了一些之前不明白的事情,或者配合其他吐槽的话会显得不耐烦。
而说 I got it. 的时候,就是纯粹表示,我知道了明白了。
今天我们就来介绍Flutter中的常用库get_it
一、来由
在Dart和Flutter工程中,为一个组件提供对象/服务的默认方式是通过InheritedWidget。
还有Provider、Singleton、IoC等方式。
1.1 InheritedWidget
如果希望一个部件或其模型能够访问服务,则组件必须是继承的组件的子组件。然后这会导致不必要的嵌套。而且依赖性强,持续性维护差。
///创建数据类 DataWidget,然后在页面中用数据类DataWidget包含页面child部分
///1、创建 DataWidget
class DataWidget extends InheritedWidget {
DataWidget({
@required this.data,
Widget child
}) :super(child: child);
final int data;
static DataWidget of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<DataWidget>();
}
@override
bool updateShouldNotify(DataWidget old) {
return old.data != data;
}
}
///2、创建 InheritedWidgetTestRoute.dart 文件
class InheritedWidgetTestRoute extends StatefulWidget {
@override
_InheritedWidgetTestRouteState createState() => new _InheritedWidgetTestRouteState();
}
class _InheritedWidgetTestRouteState extends State<InheritedWidgetTestRoute> {
int count = 0;
@override
Widget build(BuildContext context) {
return Center(
child: DataWidget( // 重点:使用DataWidget包裹
data: count,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding: const EdgeInsets.only(bottom: 20.0),
child: Text(DataWidget.of(context)
.data.toString())
),
RaisedButton(
child: Text("Increment"),
//每点击一次,将count自增,然后更新页面渲染,DataWidget的data将被更新
onPressed: () => setState(() => ++count),
)
],
),
),
);
}
}
1.2 Provider
为组件提供对象/服务,还有一种方式是使用Provider,使用比较繁琐。
///创建数据类 `CountNotifier`,然后在页面中用数据类 `CountNotifier`包裹child部分。
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 创建 Widget 持有 CountNotifier 数据
return ChangeNotifierProvider.value(
value: CountNotifier(),
child: MaterialApp(
title: 'Privoder Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: ProvidePage(title: 'Provider 测试页面'),
),
);
}
}
class ProvidePage extends StatelessWidget {
final String title;
ProvidePage({Key key, this.title}) : super(key: key);
@override
Widget build(BuildContext context) {
// 获取 CountNotifier 数据 (最简单的方式)
final counter = Provider.of<CountNotifier>(context);
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text( '按下按钮,使数字增加:', ),
Text(
'${counter.count}',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
counter.increment();
},
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
/// 核心:继承自ChangeNotifier
/// 可以单独放在一个类文件里
class CountNotifier with ChangeNotifier {
int _count = 0;
int get count => _count;
increment() {
_count++;
// 核心方法,通知刷新UI,调用build方法
notifyListeners();
}
}
1.3 Singleton、IoC
我们也可以通过其他的方式在App中的任意位置获取到要访问的对象,但是:
-
如果使用Singleton,则无法轻松地将实现切换到另一个(例如用于单元测试的模拟版本)
-
用于依赖项注入的IoC容器提供了类似的功能,但代价是启动时间慢且可读性差,因为不知道神奇注入的对象来自何处。由于大多数IoC库都依赖反射,因此它们不能与Flutter一起使用。
1.4 GetIt
在App迭代发展中,随着代码工程的增长,在某些时候需要将App的部分逻辑放在与Widget分离的类中。使Widget不具有直接依赖关系可以使代码更好地组织并更易于测试和维护。但是现在需要一种从 UI 代码访问这些对象的方法。
作者escamoteur 借鉴.net中的Service Locator Splat概念,在Dart中开发而成。
❝Service Locators 的概念,它是一种将接口(抽象基类)与具体实现解耦的方法,同时允许通过接口从您的 App 中的任何地方访问具体实现。
❞
故GetIt应运而生,从1.0到现在的7.x
二、介绍
2.1 定义
[GetIt] 官方介绍
❝This is a simple 「Service Locator」 for Dart and Flutter projects with some additional goodies highly inspired by Splat. It can be used instead of
InheritedWidgetorProviderto access objects e.g. from your UI.Typical usage:
Accessing service objects like REST API clients or databases so that they easily can be mocked. Accessing View/AppModels/Managers/BLoCs from Flutter Views ❝❞「V7.0 has some breaking changes」 Check please check the release notes to see what's new.
❞
译文:
GetIt是一个用于 Dart 和 Flutter 项目的简单服务定位器,其中包含一些受到 Splat 启发的附加功能。它可以用来代替 InheritedWidget 或 Provider 比如从你的用户界面来访问对象。
典型用法(使用场景):
-
访问 REST API 客户端或数据库等服务对象,以便轻松模拟它们 -
从 Flutter 视图访问 View/AppModels/Managers/BLoCs
简而言之
GetIt是一个简单的直接服务定位器,允许将接口与具体实现分离,并从应用程序的任何地方访问具体实现。
一句话总结
GetIt是一个工具箱。
2.2 特点
-
调用极快 (复杂度O(1)) -
易学/易用 -
不会像提供程序或 Redux 那样使用特殊的小部件来使您的 UI 树混乱以访问您的数据。
❝Redux 的设计思想很简单,就两句话。
(1)Web 应用是一个状态机,视图与状态是一一对应的。
(2)所有的状态,保存在一个对象里面。
❞
三、使用
3.1 引入
在某包下的pubspec.yaml引入框架
dependencies:
flutter:
sdk: flutter
domain_common:
path: ../domain_common
res_common:
path: ../res_common
provider: ^5.0.0
get_it: ^7.1.3
3.2 获取
GetIt以单例模式使用
GetIt getIt = GetIt.instance;
//or
GetIt getIt = GetIt.I;
//重新自定义一个新的
GetIt getIt = GetIt.asNewInstance();
3.3 注册
GetIt locator = GetIt.instance;
//在GetIt里注册工厂类Mutou
locator.registerFactory<Mutou>(() => MutouImpl());
//在GetIt里注册单例
locator.registerSingleton<FileSystem>(() => LocalFileSystem());
locator.registerLazySingleton<ClientRepository>(() => ClientRepositoryImpl());
3.4 调用
GetIt locator = GetIt.instance;
//调用工厂对象
var mutouModel = locator.get<Mutou>();
//调用单例
var fileSystemSingleton = locator<FileSystem>();
3.5 注册方式
有三种常用的注册方式
GetIt locator = GetIt.instance;
Future<void> setupLocator({bool test = false}) async {
// 一,单例模式,使用 registerSingleton
// 在main.dart中调用setupLocator()时,就会立即注册FileSystem
// 注册单例,保证每一次调用FileSystem工具时,都是同一个
locator.registerSingleton<FileSystem>(FileSystem());
//有扩展函数可以传参可以异步
//locator.registerFactoryParam
//locator.registerFactoryAsync
//locator.registerFactoryParamAsync
//还可以扩展依赖
//locator.registerSingletonWithDependencies
// 二,懒汉模式单例,使用 registerLazySingleton
// 调用setupLocator()时,没有生成,直到在其他地方第一次调用ClientRepository工具时,才会注册
// registerSingleton注册单例,保证每一次调用ClientRepository工具时,都是同一个
locator.registerLazySingleton<ClientRepository>(ClientRepositoryImpl());
//也支持异步模式
//locator.registerLazySingletonAsync
// 三, 工厂模式, 使用 registerFactory / registerFactoryParam / registerFactoryParamAsync
// 调用setupLocator()时,没有生成,直到在其他地方第一次调用Mutou工具时,才会注册
// 参数必须时工厂函数,工厂函数不需要参数用registerFactory
// 参数必须时工厂函数,工厂函数需要参数用registerFactoryParam
// 参数必须时工厂函数,工厂函数是异步且需要参数用registerFactoryParamAsync
locator.registerFactory<Mutou>( () => MutouImpl()) );
locator.registerFactoryParam<MutouPlus,String,int>((str,num)
=> MutouPlusImpl(param1:str, param2: num));
locator.registerFactoryParamAsync<MutouPro,String,int>((str,num)
=> MutouProImpl(param1:str, param2: num));
}
3.6 其他
//判断某个对象是否已注册
bool isRegistered<T extends Object>({Object? instance, String? instanceName});
//重置所有注册的对象
Future<void> reset({bool dispose = true});
//画个圈V5.0后才有
//是否范围内重置
Future<void> resetScope({bool dispose = true});
//之前的所有注册作废,在一个新的范围内新建
void pushNewScope({
void Function(GetIt getIt)? init,
String? scopeName,
ScopeDisposeFunc? dispose,
});
//在某个范围内退出,在这个范围内注册的所有对象作废
Future<void> popScope();
//若有多个范围,则制定某个范围内退出,相应范围注册的所有对象作废
Future<bool> popScopesTill(String name);
//获取当前范围名字
String? get currentScopeName;
//在特定时间timeout内完成所有异步对象创建,ignorePendingAsyncCreation是是否忽略异步对象的创建
Future<void> allReady({
Duration? timeout,
bool ignorePendingAsyncCreation = false,
});
//在特定timeout时长下,是否完成异步的对象创建
Future<void> isReady<T extends Object>({
Object? instance,
String? instanceName,
Duration? timeout,
Object? callee,
});
//不要被这个名字误解了,这个还是检查异步对象是否被创建,因为同步的不需要检查
bool isReadySync<T extends Object>({
Object? instance,
String? instanceName,
});
//检查是否完成所有异步对象创建,ignorePendingAsyncCreation是是否忽略异步对象的创建
bool allReadySync([bool ignorePendingAsyncCreation = false]);
// 注册为异步对象的时候,在init函数里手动调动
/// If you want to use this mechanism you have to pass [signalsReady==true] when registering
/// the Singleton.
/// Typically this is used in this way inside the registered objects init
/// method `GetIt.instance.signalReady(this);`
void signalReady(Object? instance);
四、源码
本文以 get_it: ^7.1.3为样本分析
4.1 本身
本身是一个服务工厂或工具箱类
class _ServiceFactory<T extends Object, P1, P2> {
final _ServiceFactoryType factoryType;
//里面的单例
final _GetItImplementation _getItInstance;
late final Type param1Type;
late final Type param2Type;
//存储各种对象
/// Because of the different creation methods we need alternative factory functions
/// only one of them is always set.
final FactoryFunc<T>? creationFunction;
final FactoryFuncAsync<T>? asyncCreationFunction;
final FactoryFuncParam<T, P1?, P2?>? creationFunctionParam;
final FactoryFuncParamAsync<T, P1?, P2?>? asyncCreationFunctionParam;
/// Dispose function that is used when a scope is popped
final DisposingFunc<T>? disposeFunction;
/// In case of a named registration the instance name is here stored for easy access
final String? instanceName;
/// true if one of the async registration functions have been used
final bool isAsync;
/// If a an existing Object gets registered or an async/lazy Singleton has finished
/// its creation it is stored here
Object? instance;
/// the type that was used when registering. used for runtime checks
late final Type registrationType;
/// to enable Singletons to signal that they are ready (their initialization is finished)
late Completer _readyCompleter;
/// the returned future of pending async factory calls or factory call with dependencies
Future<T>? pendingResult;
/// If other objects are waiting for this one
/// they are stored here
final List<Type> objectsWaiting = [];
bool get isReady => _readyCompleter.isCompleted;
bool get isNamedRegistration => instanceName != null;
String get debugName => '$instanceName : $registrationType';
bool get canBeWaitedFor =>
shouldSignalReady || pendingResult != null || isAsync;
final bool shouldSignalReady;
_ServiceFactory(
this._getItInstance,
this.factoryType, {
this.creationFunction,
this.asyncCreationFunction,
this.creationFunctionParam,
this.asyncCreationFunctionParam,
this.instance,
this.isAsync = false,
this.instanceName,
required this.shouldSignalReady,
this.disposeFunction,
}) : assert(
!(disposeFunction != null &&
instance != null &&
instance is Disposable),
' You are trying to register type ${instance.runtimeType.toString()} '
'that implements "Disposable" but you also provide a disposing function') {
registrationType = T;
param1Type = P1;
param2Type = P2;
_readyCompleter = Completer();
}
4.2 注册
以常用的registerLazySingleton为例看下注册的源码
@override
void registerLazySingleton<T>(FactoryFunc<T> func, {String instanceName}) {
_register<T, void, void>(
type: _ServiceFactoryType.lazy,
instanceName: instanceName,
factoryFunc: func,
isAsync: false,
shouldSignalReady: false);
}
void _register<T, P1, P2>({
@required _ServiceFactoryType type,
FactoryFunc<T> factoryFunc,
FactoryFuncParam<T, P1, P2> factoryFuncParam,
FactoryFuncAsync<T> factoryFuncAsync,
FactoryFuncParamAsync<T, P1, P2> factoryFuncParamAsync,
T instance,
@required String instanceName,
@required bool isAsync,
Iterable<Type> dependsOn,
@required bool shouldSignalReady,
}) {
//......参数判断已忽略
final serviceFactory = _ServiceFactory<T, P1, P2>(
type,
creationFunction: factoryFunc,
creationFunctionParam: factoryFuncParam,
asyncCreationFunctionParam: factoryFuncParamAsync,
asyncCreationFunction: factoryFuncAsync,
instance: instance,
isAsync: isAsync,
instanceName: instanceName,
shouldSignalReady: shouldSignalReady,
);
if (instanceName == null) {
_factories[T] = serviceFactory;
} else {
_factoriesByName[instanceName] = serviceFactory;
}
// 普通单例立即被创建注册
if (type == _ServiceFactoryType.constant &&
!shouldSignalReady &&
!isAsync &&
(dependsOn?.isEmpty ?? false)) {
serviceFactory.instance = factoryFunc();
serviceFactory._readyCompleter.complete();
return;
}
//若是异步或有其他依赖函数创建后的单例,要检查它若依赖于其他单例被创建后再创建
if ((isAsync || (dependsOn?.isNotEmpty ?? false)) &&
type == _ServiceFactoryType.constant) {
/// Any client awaiting the completion of this Singleton
/// Has to wait for the completion of the Singleton itself as well
/// as for the completion of all the Singletons this one depends on
/// For this we use [outerFutureGroup]
/// A `FutureGroup` completes only if it's closed and all contained
/// Futures have completed
final outerFutureGroup = FutureGroup();
Future dependentFuture;
if (dependsOn?.isNotEmpty ?? false) {
//有异步依赖 就 等待单例外部依赖的函数完成稿后
final dependentFutureGroup = FutureGroup();
for (final type in dependsOn) {
final dependentFactory = _factories[type];
throwIf(dependentFactory == null,
ArgumentError('Dependent Type $type is not registered in GetIt'));
throwIfNot(dependentFactory.canBeWaitedFor,
ArgumentError('Dependent Type $type is not an async Singleton'));
dependentFactory.objectsWaiting.add(serviceFactory.registrationType);
dependentFutureGroup.add(dependentFactory._readyCompleter.future);
}
dependentFutureGroup.close();//依赖的函数完成了
dependentFuture = dependentFutureGroup.future;
} else {//无依赖直接执行
dependentFuture = Future.sync(() {});
}
outerFutureGroup.add(dependentFuture);
/// if someone uses getAsync on an async Singleton that has not be started to get created
/// because its dependent on other objects this doesn't work because [pendingResult] is not set in
/// that case. Therefore we have to set [outerFutureGroup] as [pendingResult]
dependentFuture.then((_) {
Future<T> isReadyFuture;
if (!isAsync) {//非异步,单例有依赖
serviceFactory.instance = factoryFunc();
isReadyFuture = Future<T>.value(serviceFactory.instance as T);
if (!serviceFactory.shouldSignalReady) {//单例未创建完成
//标记为完成
serviceFactory._readyCompleter.complete();
}
} else {
//异步单例
final asyncResult = factoryFuncAsync();
isReadyFuture = asyncResult.then((instance) {
serviceFactory.instance = instance;
if (!serviceFactory.shouldSignalReady && !serviceFactory.isReady) {
serviceFactory._readyCompleter.complete();
}
return instance;
});
}
outerFutureGroup.add(isReadyFuture);
outerFutureGroup.close();
});
//等待返回的结果是outerFutureGroup列表中的最近一个
serviceFactory.pendingResult =
outerFutureGroup.future.then((completedFutures) {
return completedFutures.last as T;
});
}
}
4.3 调用
以调用get函数的源码分析
T get<T extends Object>({
String? instanceName,
dynamic param1,
dynamic param2,
}) {
//一、获取数组 _findFactoryByNameOrType
final instanceFactory = _findFactoryByNameAndType<T>(instanceName);
Object instance = Object; //late
if (instanceFactory.isAsync || instanceFactory.pendingResult != null) {
/// We use an assert here instead of an `if..throw` for performance reasons
assert(
instanceFactory.factoryType == _ServiceFactoryType.constant ||
instanceFactory.factoryType == _ServiceFactoryType.lazy,
"You can't use get with an async Factory of ${instanceName ?? T.toString()}.",
);
assert(
instanceFactory.isReady,
'You tried to access an instance of ${instanceName ?? T.toString()} that is not ready yet',
);
instance = instanceFactory.instance!;
} else {
//二、数组中获取对应对象
instance = instanceFactory.getObject(param1, param2);
}
assert(
instance is T,
'Object with name $instanceName has a different type '
'(${instanceFactory.registrationType.toString()}) than the one that is inferred '
'(${T.toString()}) where you call it',
);
return instance as T;
}
//一、获取数组 _findFactoryByNameOrType
/// Is used by several other functions to retrieve the correct [_ServiceFactory]
_ServiceFactory _findFactoryByNameAndType<T extends Object>(
String? instanceName, [
Type? type,
]) {
final instanceFactory =
_findFirstFactoryByNameAndTypeOrNull<T>(instanceName, type: type);
assert(
instanceFactory != null,
// ignore: missing_whitespace_between_adjacent_strings
'Object/factory with ${instanceName != null ? 'with name $instanceName and ' : ' '}'
'type ${T.toString()} is not registered inside GetIt. '
'\n(Did you accidentally do GetIt sl=GetIt.instance(); instead of GetIt sl=GetIt.instance;'
'\nDid you forget to register it?)',
);
return instanceFactory!;
}
/// Is used by several other functions to retrieve the correct [_ServiceFactory]
_ServiceFactory<T, dynamic, dynamic>?
_findFirstFactoryByNameAndTypeOrNull<T extends Object>(
String? instanceName,
{Type? type,
bool lookInScopeBelow = false}) {
/// We use an assert here instead of an `if..throw` because it gets called on every call
/// of [get]
/// `(const Object() is! T)` tests if [T] is a real type and not Object or dynamic
assert(
type != null || const Object() is! T,
'GetIt: The compiler could not infer the type. You have to provide a type '
'and optionally a name. Did you accidentally do `var sl=GetIt.instance();` '
'instead of var sl=GetIt.instance;',
);
_ServiceFactory<T, dynamic, dynamic>? instanceFactory;
int scopeLevel = _scopes.length - (lookInScopeBelow ? 2 : 1);
//快速查找
while (instanceFactory == null && scopeLevel >= 0) {
// 注意:factoriesByName = <String?, Map<Type, _ServiceFactory<Object, dynamic, dynamic>>>{};
// factoriesByName这是个Map,所以复杂度是O(1))
final factoryByTypes = _scopes[scopeLevel].factoriesByName[instanceName];
if (type == null) {
instanceFactory = factoryByTypes != null
? factoryByTypes[T] as _ServiceFactory<T, dynamic, dynamic>?
: null;
} else {
/// in most cases we can rely on the generic type T because it is passed
/// in by callers. In case of dependent types this does not work as these types
/// are dynamic
instanceFactory = factoryByTypes != null
? factoryByTypes[type] as _ServiceFactory<T, dynamic, dynamic>?
: null;
}
scopeLevel--;
}
return instanceFactory;
}
//二、数组中获取对应对象
/// returns an instance depending on the type of the registration if [async==false]
T getObject(dynamic param1, dynamic param2) {
assert(
!(factoryType != _ServiceFactoryType.alwaysNew &&
(param1 != null || param2 != null)),
'You can only pass parameters to factories!');
try {
switch (factoryType) {
//始终创建最新的模式
case _ServiceFactoryType.alwaysNew:
if (creationFunctionParam != null) {
return creationFunctionParam(param1 as P1, param2 as P2);
} else {
return creationFunction();
}
break;
//常量模式
case _ServiceFactoryType.constant:
return instance as T;
break;
//懒汉模式
case _ServiceFactoryType.lazy:
if (instance == null) {
instance = creationFunction();
_readyCompleter.complete();
}
return instance as T;
break;
default:
throw StateError('Impossible factoryType');
}
} catch (e, s) {
print('Error while creating ${T.toString()}');
print('Stack trace:\n $s');
rethrow;
}
}
总结
GetIt作为一个常用的动态服务定位器,一个工具箱,摆脱了InheritedWidget、Provider、Singleton、IoC等苛刻繁琐的流程,简单易用,脱颖而出。
作为Flutter开发者,经常流于表面的使用,当静下心来分析其源码,自有一套完整健全的逻辑。
作为使用者,管中窥豹,时见一斑。
作为剖析者,细品,别有一番风味。
