当前位置: 首页 > 工具软件 > GetX > 使用案例 >

Flutter 从源码看Getx的依赖原理

危飞文
2023-12-01

使用篇

原理篇

一、Get的依赖注入源码解析

1、 Get.put

每次我们存一个对象的时候都会使用Get.put()。要用的时候都是Get.find()。
那么Getx是如何将我们需要的对象保存起来?而且还可以跨页面共享数据的呢?
接下来,带着疑问去源码寻找我们需要的答案。

首先我们来看一看我们put的时候的代码

  S put<S>(S dependency,
          {String? tag,
          bool permanent = false,
          InstanceBuilderCallback<S>? builder}) =>
      GetInstance().put<S>(dependency, tag: tag, permanent: permanent);
extension Inst on GetInterface {

这块代码是 在一个叫Inst的 扩展类里。他是对GetInterface的拓展。

接着我们看看GetInstance类的put方法

  S put<S>(
    S dependency, {
    String? tag,
    bool permanent = false,
    @deprecated InstanceBuilderCallback<S>? builder,
  }) {
    _insert(
        isSingleton: true,
        name: tag,
        permanent: permanent,
        builder: builder ?? (() => dependency));
    return find<S>(tag: tag);
  }

看到上面isSingleton这个参数是true,说明这个put方法永远是单例的对象。如果不想单例的话
可以使用Get.create()这个方法,每次find时候都会新建一个实例对象,从代码可以看出isSingleton是false。

  void create<S>(
    InstanceBuilderCallback<S> builder, {
    String? tag,
    bool permanent = true,
  }) {
    _insert(
      isSingleton: false,
      name: tag,
      builder: builder,
      permanent: permanent,
    );
  }

接着可以看到又调用了insert方法

  void _insert<S>({
    bool? isSingleton,
    String? name,
    bool permanent = false,
    required InstanceBuilderCallback<S> builder,
    bool fenix = false,
  }) {
    final key = _getKey(S, name);

    if (_singl.containsKey(key)) {
      final dep = _singl[key];
      if (dep != null && dep.isDirty) {
        _singl[key] = _InstanceBuilderFactory<S>(
          isSingleton,
          builder,
          permanent,
          false,
          fenix,
          name,
          lateRemove: dep as _InstanceBuilderFactory<S>,
        );
      }
    } else {
      _singl[key] = _InstanceBuilderFactory<S>(
        isSingleton,
        builder,
        permanent,
        false,
        fenix,
        name,
      );
    }
  }

我们一句一句的看
首先通过name(就是我们传入的tag)和 S (我们注入的数据类型)去获取key。一般情况下是没有设置tag的。所以,我们使用put的时候,然后find拿到的都是同一对象。如果设置了tag的那么就会生成相同类型但是实例对象不同。从上面的源码看就可以清楚了。

  String _getKey(Type type, String? name) {
    return name == null ? type.toString() : type.toString() + name;
  }

name为空的时候直接使用S的数据类型作为key,不为空的时候直接两者的拼接。
第2行代码是if判断。判断key是否包含在_singl里。_singl是一个以String为key,_InstanceBuilderFactory为value的Map对象。

 static final Map<String, _InstanceBuilderFactory> _singl = {};
  1. 如果key不存在_singl里,就直接新建一个对象赋值给当前的key。
  2. 如果key存在_singl里,直接通过key获取对象。接着又判断获取的对象不为空并且这个对象是脏对象才对已存在key的对象进行重新赋值。否则的话就不管。

那什么情况是脏对象呢?看看下面的源码就可以知道当widget被dispose的时候,这个put的对象就会被标记为脏,然后回调删除的方法,将标记为脏的对象进行回收。


  static void reportRouteWillDispose(Route disposed) {
    final keysToRemove = <String>[];

    _routesKey[disposed]?.forEach(keysToRemove.add);

    /// Removes `Get.create()` instances registered in `routeName`.
    if (_routesByCreate.containsKey(disposed)) {
      for (final onClose in _routesByCreate[disposed]!) {
        // assure the [DisposableInterface] instance holding a reference
        // to onClose() wasn't disposed.
        onClose();
      }
      _routesByCreate[disposed]!.clear();
      _routesByCreate.remove(disposed);
    }

    for (final element in keysToRemove) {
      GetInstance().markAsDirty(key: element);

      //_routesKey.remove(element);
    }

    keysToRemove.clear();
  }
  
  void markAsDirty<S>({String? tag, String? key}) {
    final newKey = key ?? _getKey(S, tag);
    if (_singl.containsKey(newKey)) {
      final dep = _singl[newKey];
      if (dep != null && !dep.permanent) {
        dep.isDirty = true;
      }
    }
  }

好了,insert方法这部分看完了,这部分主要是将数据存储起来。后面发现他接着调用了find方法。
一般来讲我们都会直接将插入的对象进行返回,但是呢Getx却要再费力的find方法。这可能让人有点奇怪。接下去看就知道了。

  S find<S>({String? tag}) {
    final key = _getKey(S, tag);
    if (isRegistered<S>(tag: tag)) {
      final dep = _singl[key];
      if (dep == null) {
        if (tag == null) {
          throw 'Class "$S" is not registered';
        } else {
          throw 'Class "$S" with tag "$tag" is not registered';
        }
      }
      final i = _initDependencies<S>(name: tag);
      return i ?? dep.getDependency() as S;
    } else {
      // ignore: lines_longer_than_80_chars
      throw '"$S" not found. You need to call "Get.put($S())" or "Get.lazyPut(()=>$S())"';
    }
  }

还是通过tag和S来生成key。接着又是一个if判断,如果要查找的对象已经注册了。
就可以通过key拿到注册的对象。我们直接看不为空的情况。调用了_initDependencies方法
我们看看源码

  S? _initDependencies<S>({String? name}) {
    final key = _getKey(S, name);
    final isInit = _singl[key]!.isInit;
    S? i;
    if (!isInit) {
      i = _startController<S>(tag: name);
      if (_singl[key]!.isSingleton!) {
        _singl[key]!.isInit = true;
        if (Get.smartManagement != SmartManagement.onlyBuilder) {
          RouterReportManager.reportDependencyLinkedToRoute(_getKey(S, name));
        }
      }
    }
    return i;
  }

判断实例对象是不是首次初始化,如果不是直接返回对象。
是的话就开始controller对象初始化操作。

  /// 初始化 controller
  S _startController<S>({String? tag}) {
    final key = _getKey(S, tag);
    final i = _singl[key]!.getDependency() as S;
    if (i is GetLifeCycleBase) {
      i.onStart();
      if (tag == null) {
        Get.log('Instance "$S" has been initialized');
      } else {
        Get.log('Instance "$S" with tag "$tag" has been initialized');
      }
      if (!_singl[key]!.isSingleton!) {
        RouterReportManager.appendRouteByCreate(i);
      }
    }
    return i;
  }

我们看这一句代码 final i = _singl[key]!.getDependency() as S;
这个实例对象调用自身的一个方法getDependency。我们看看他是做什么的?

  S getDependency() {
    if (isSingleton!) {
      if (dependency == null) {
        _showInitLog();
        dependency = builderFunc();
      }
      return dependency!;
    } else {
      return builderFunc();
    }
  }

首选判断他是不是单例的,如果是直接新建一个对象,赋值给dependency 存储起来,下次find的时候就可以直接返回对象了。如果不是,每次就会新建一个对象。
接着下一句代码 if (i is GetLifeCycleBase)
这个if判断就是controller绑定widget生命周期的关键。判断put的对象类型是不是GetLifeCycleBase的子类。我写controller的时候是不是要继承一个GetxController。我们看看下面的类继承关系就知道了

1. abstract class GetxController extends DisposableInterface
    with ListenableMixin, ListNotifierMixin {}

2. abstract class DisposableInterface extends GetLifeCycle {}

3. abstract class GetLifeCycle with GetLifeCycleBase {
  GetLifeCycle() {
    $configureLifeCycle();
  }
}

从上面的继承关系我们就可以很清楚的了解到,我们建的controller类只要是继承GetxController的都是GetLifeCycleBase 的子类。
接着调用onStart()方法,这个主要是初始化controller的相应生命周期的
onInit,onReady,onClose。

if (!_singl[key]!.isSingleton!) {
        RouterReportManager.appendRouteByCreate(i);
      }

这句的意思是如果不是单例对象,就是用Get.create()创建的实例的才会调用这个方法。

  static void appendRouteByCreate(GetLifeCycleBase i) {
    _routesByCreate[_current] ??= HashSet<Function>();
    // _routesByCreate[Get.reference]!.add(i.onDelete as Function);
    _routesByCreate[_current]!.add(i.onDelete);
  }

这个方法作用就是将资源回收与路由关联起来,等到widget被disposed就会回调controller的onClose方法。
接着看_startController之后的代码

        _singl[key]!.isInit = true;
        if (Get.smartManagement != SmartManagement.onlyBuilder) {
          RouterReportManager.reportDependencyLinkedToRoute(_getKey(S, name));
        }

将isInit 设置为true,然后下次就不会再执行这段代码了,只初始化一次。
调用RouterReportManager类的reportDependencyLinkedToRoute方法。
这段代码主要是用来将controller类关联路由的。

  • SmartManagement.full 这是默认的。销毁那些没有被使用的、没有被设置为永久的类。在大多数情况下,你会希望保持这个配置不受影响。如果你是第一次使用GetX,那么不要改变这个配置。

  • SmartManagement.onlyBuilders 使用该选项,只有在init:中启动的控制器或用Get.lazyPut()加载到Binding中的控制器才会被销毁。
    如果你使用Get.put()或Get.putAsync()或任何其他方法,SmartManagement将没有权限移除这个依赖。
    在默认行为下,即使是用 "Get.put
    "实例化的widget也会被移除,这与SmartManagement.onlyBuilders不同。

  • SmartManagement.keepFactory 就像SmartManagement.full一样,当它不再被使用时,它将删除它的依赖关系,但它将保留它们的工厂,这意味着如果你再次需要该实例,它将重新创建该依赖关系。

2、Get.lazyPut

Get.put与Get.lazyPut的对比

  S put<S>(
    S dependency, {
    String? tag,
    bool permanent = false,
    @deprecated InstanceBuilderCallback<S>? builder,
  }) {
    _insert(
        isSingleton: true,
        name: tag,
        permanent: permanent,
        builder: builder ?? (() => dependency));
    return find<S>(tag: tag);
  }

  void lazyPut<S>(
    InstanceBuilderCallback<S> builder, {
    String? tag,
    bool? fenix,
    bool permanent = false,
  }) {
    _insert(
      isSingleton: true,
      name: tag,
      permanent: permanent,
      builder: builder,
      fenix: fenix ?? Get.smartManagement == SmartManagement.keepFactory,
    );
  }

通过对比发现,put是直接传入一个实例对象,而lazyPut是一个builder对象
put是直接find返回一个对象,而lazyPut没有,只有当你需要的时候也就是你使用了Get.find才会新建一个实例对象,所以懒加载也就是这个意思。

3、Get.create

  void create<S>(
    InstanceBuilderCallback<S> builder, {
    String? tag,
    bool permanent = true,
  }) {
    _insert(
      isSingleton: false,
      name: tag,
      builder: builder,
      permanent: permanent,
    );
  }

我们看到了permanent设置了true。表示该对象持久存在。
我们看看如下源码,可以看到在回收对象的方法上使用到了,接着往下看。
GetInstance类的delete方法

 bool delete<S>({String? tag, String? key, bool force = false}) {
    final newKey = key ?? _getKey(S, tag);

    if (!_singl.containsKey(newKey)) {
      Get.log('Instance "$newKey" already removed.', isError: true);
      return false;
    }

    final dep = _singl[newKey];

    if (dep == null) return false;

    final _InstanceBuilderFactory builder;
    if (dep.isDirty) {
      builder = dep.lateRemove ?? dep;
    } else {
      builder = dep;
    }

    if (builder.permanent && !force) {
      Get.log(
        // ignore: lines_longer_than_80_chars
        '"$newKey" has been marked as permanent, SmartManagement is not authorized to delete it.',
        isError: true,
      );
      return false;
    }
    final i = builder.dependency;

    if (i is GetxServiceMixin && !force) {
      return false;
    }

    if (i is GetLifeCycleBase) {
      i.onDelete();
      Get.log('"$newKey" onDelete() called');
    }

    if (builder.fenix) {
      builder.dependency = null;
      builder.isInit = false;
      return true;
    } else {
      if (dep.lateRemove != null) {
        dep.lateRemove = null;
        Get.log('"$newKey" deleted from memory');
        return false;
      } else {
        _singl.remove(newKey);
        if (_singl.containsKey(newKey)) {
          Get.log('Error removing object "$newKey"', isError: true);
        } else {
          Get.log('"$newKey" deleted from memory');
        }
        return true;
      }
    }
  }

我们拿出一段代码看一下,可以看到permanet为true时,整个就返回false了,就不执行后面的对象删除操作了,是不是很清楚了。

    if (builder.permanent && !force) {
      Get.log(
        // ignore: lines_longer_than_80_chars
        '"$newKey" has been marked as permanent, SmartManagement is not authorized to delete it.',
        isError: true,
      );
      return false;
    }

4、 Get.putAsync

 Future<S> putAsync<S>(
    AsyncInstanceBuilderCallback<S> builder, {
    String? tag,
    bool permanent = false,
  }) async {
    return put<S>(await builder(), tag: tag, permanent: permanent);
  }

可以看到除了builder设置为异步之外,跟put没什么不同。

二、总结

从源码来看的话,Getx的本质就是使用Map来保持一种依赖关系。通过使用find就能够找到相应的对象。
最后想说的就是,熟悉源码能帮助我们更好的使用框架。如果对你有用的话,请不要吝啬给个赞吧!

 类似资料: