• Flutter 从源码看Getx的依赖原理


    使用篇
    原理篇

    一、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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    extension Inst on GetInterface {
    
    • 1

    这块代码是 在一个叫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);
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    看到上面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,
        );
      }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    接着可以看到又调用了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,
          );
        }
      }
    
    • 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
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

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

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

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

     static final Map<String, _InstanceBuilderFactory> _singl = {};
    
    • 1
    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;
          }
        }
      }
    
    • 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
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    好了,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())"';
        }
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    还是通过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;
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    判断实例对象是不是首次初始化,如果不是直接返回对象。
    是的话就开始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;
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

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

      S getDependency() {
        if (isSingleton!) {
          if (dependency == null) {
            _showInitLog();
            dependency = builderFunc();
          }
          return dependency!;
        } else {
          return builderFunc();
        }
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    首选判断他是不是单例的,如果是直接新建一个对象,赋值给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();
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

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

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

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

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

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

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

    将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);
      }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
      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,
        );
      }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    通过对比发现,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,
        );
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    我们看到了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;
          }
        }
      }
    
    • 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
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58

    我们拿出一段代码看一下,可以看到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;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    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);
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

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

    二、总结

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

  • 相关阅读:
    在CentOS7上增加swap空间
    计算机毕业设计springboot+vue基本微信小程序的校园跑腿平台 uniapp
    Java学习笔记38——网络编程02
    基于TF-IDF与逻辑回归模型实现文本实体关系抽取
    Go语言开发环境搭建
    【CesiumJS-5】绘制动态路线实现飞行航线、汽车轨迹、路径漫游等
    基于微信小程序的高校餐厅食品留样管理系统设计与实现-计算机毕业设计源码+LW文档
    DP-Laplace Mechanism
    CSS padding(填充)
    NetworkManager 图形化配置 bond
  • 原文地址:https://blog.csdn.net/hjjdehao/article/details/126511436