Unity-IOC
Posted kakashi8841
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Unity-IOC相关的知识,希望对你有一定的参考价值。
本文目的
向大家介绍:
- 在开发中为何要使用
IoC
- 如何从0开始实现一个精简的
IoC
- 使用
IoC
前后代码带来怎样的变化 - 我当前在开发的
IoC
类库
如果你对
1
、2
、3
都已经很熟了,并且对我的项目感兴趣,可以直接跳我的IoC
仓库.完整的工程地址在https://github.com/kakashiio/Unity-IOC,该IoC
仓库也是我的Unity游戏框架计划https://github.com/kakashiio/Unity-SourceFramework中的一部分.
为什么要使用IoC
想象一下,当你在实现一个UI管理器UIManager
时,当在UIManager
中需要加载UI资源时,你是通过何种方式加载资源的.
一般开发诸如AssetManager
、TimeManager
、EventManager
等管理器(Manager)
时.喜欢采用静态方法或单例.这样做是为了使得项目能方便地引用这些管理器.
常见的实现代码:
public class UIManager
public void Create<T>(Action<T> onCreate) where T : IUI
string assetPath = _GetAssetPath<T>();
AssetManager.Instantiate<GameObject>((go)=>
var t = new T();
t.Init(go);
onCreate?.Invoke(t);
);
或
public class UIManager
public void Create<T>(Action<T> onCreate) where T : IUI
string assetPath = _GetAssetPath<T>();
Singleton<AssetManager>.Instance.Instantiate<GameObject>((go)=>
var t = new T();
t.Init(go);
onCreate?.Invoke(t);
);
虽然静态方法或单例都能实现想要的效果,但或多或少会带来负面的效果.比如耦合严重,难以测试等等.因此本文引入一种已经很成熟的设计思路IoC
,一步步实现一个简单的IoC
容器,并且将IoC
应用到实际中.大家也可以对比感受引入IoC
前后代码发生的变化.
IoC简述
IoC(Inversion of Control,控制反转)
通常也被称为DI(Dependency Injection,依赖注入)
.他是将传统对象依赖从内部指定改为外部决定的过程.比如上面的UIManager
中内部指定了使用AssetManager
.当使用IoC
设计时,代码会修改为:
public class UIManager
private IAssetManager _AssetManager;
public UIManager(IAssetManager assetManager)
_AssetManager = assetManager;
public void Create<T>(Action<T> onCreate) where T : IUI
string assetPath = _GetAssetPath<T>();
_AssetManager.Instantiate<GameObject>((go)=>
var t = new T();
t.Init(go);
onCreate?.Invoke(t);
);
这是引入IoC
最简单的例子,即把内部采用哪个IAssetManager
实现的权力转移给外部,因此称为IoC(Inversion of Control,控制反转)
,由于UIManager
依赖了IAssetManager
而且将其实现通过外部构造传入,因此也称DI(Dependency Injection,依赖注入)
.
但是这样的代码明显不够方便,因为需要自己在构造时传入IAssetManager
,如果只是UIManager
需要传入IAssetManager
实例还好,实际上可以预见的是SceneManager
、UnitManager
、EffectManager
等类可能都需要IAssetManager
,那么最终可能会有类似这样的代码:
public class Main
public void Init()
var assetManager = new AssetManager();
var uiManager = new UIManager(assetManager);
var sceneManager = new SceneManager(assetManager);
var unitManager = new UnitManager(assetManager);
var effectManager = new EffectManager(assetManager);
// ...
这样的代码重复、而且没有意义、不同的人反复在这里添加自己的代码也容易引发冲突和错误.我们应该编写一个更智能的IoC
框架来帮助我们完成这些事情.
编写IoC框架
添加依赖
由于我们需要大量使用反射完成一些工作,因此通过PackageManager依赖我之前开源的用于反射的Packagehttps://github.com/kakashiio/Unity-Reflection
打开Unity的PackageManager并点击左上角的“+”
按钮,选择"Add package from git URL..."
并填入该地址https://github.com/kakashiio/Unity-Reflection.git#1.0.0
IoC容器
定义IoC容器接口
public interface IIOCContainer
/// <summary>
/// 实例化`type`类型的对象并注入其所有字段和属性
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
object InstanceAndInject(Type type);
/// <summary>
/// 实例化类型为`T`的对象并注入其所有字段和属性
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
T InstanceAndInject<T>();
/// <summary>
/// 为一个已存在的对象`obj`注入其所有字段和属性
/// </summary>
/// <param name="obj"></param>
/// <param name="recursive">
/// 如果recursive == true, 那么instance的字段也会被递归注入
/// </param>
void Inject(object obj, bool recursive = false);
/// <summary>
/// 查找`type`类型或`type`类型子类的对象.
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
object FindObjectOfType(Type type);
/// <summary>
/// 查找`T`类型或`T`类型子类的对象.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
T FindObjectOfType<T>() where T : class;
/// <summary>
/// 查找所有`type`类型或`type`类型子类的对象.
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
List<object> FindObjectsOfType(Type type);
/// <summary>
/// 查找所有`T`类型或`T`类型子类的对象.
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
List<T> FindObjectsOfType<T>() where T : class;
IIOCContainer
接口主要定义了一个IOC容器对外提供的服务.比如外部可以通过FindObjectOfType
查找某个类型在容器中创建的实例、或者通过InstanceAndInject
创建一个指定类型的对象,InstanceAndInject
方法与new
创建对象不同在于InstanceAndInject
创建的对象会被容器管理,同时会自动按设计的约定注入字段.
这里每个方法都写了比较详细的注释.如果目前大家还不是很清楚,主要可能是对于IoC
还不太熟悉,这关系不大.后面会通过实际使用的例子回过来深入介绍细节.接下来先把该接口的实现和另外几个比较重要的类的源码给出来,目前大家只要先大概浏览一下即可.
实现IoC容器
public class IOCContainer : IIOCContainer
private ITypeContainer _TypeContainer;
private List<object> _Instances = new List<object>();
private HashSet<object> _InjectedObj = new HashSet<object>();
private Dictionary<Type, object> _FindCache = new Dictionary<Type, object>();
public IOCContainer(ITypeContainer typeContainer)
_TypeContainer = typeContainer;
var inheritedFromIOCComponent = Reflections.GetTypes(_TypeContainer, typeof(IOCComponent));
var typesWithIOCComponent = Reflections.GetTypesWithAttributes(_TypeContainer, inheritedFromIOCComponent);
foreach (var type in typesWithIOCComponent)
_Instances.Add(_Instance(type));
// Inject all type's field or property
foreach (var instance in _Instances)
Inject(instance);
public object InstanceAndInject(Type type)
var instance = _Instance(type);
Inject(instance);
return instance;
public T InstanceAndInject<T>()
return (T) InstanceAndInject(typeof(T));
public void Inject(object obj, bool recursive = false)
if (obj == null)
return;
if (obj.GetType().IsPrimitive)
return;
if (recursive)
if (_InjectedObj.Contains(obj))
return;
_InjectedObj.Add(obj);
var propertiesOrFields = Reflections.GetPropertiesAndFields<Autowired>(obj);
foreach (var propertyOrField in propertiesOrFields)
var fieldValue = FindObjectOfType(propertyOrField.GetFieldOrPropertyType());
propertyOrField.SetValue(obj, fieldValue);
if (recursive)
Inject(fieldValue, true);
public object FindObjectOfType(Type type)
if (_FindCache.ContainsKey(type))
return _FindCache[type];
foreach (object instance in _Instances)
if(type.IsAssignableFrom(instance.GetType()))
_FindCache.Add(type, instance);
return instance;
return null;
public T FindObjectOfType<T>() where T : class
return FindObjectOfType(typeof(T)) as T;
public List<object> FindObjectsOfType(Type type)
return _FindObjectsOfType(typeof(object), o => o);
public List<T> FindObjectsOfType<T>() where T : class
return _FindObjectsOfType(typeof(T), o => o as T);
private object _Instance(Type type)
return Activator.CreateInstance(type);
private List<T> _FindObjectsOfType<T>(Type type, Func<object, T> mapper) where T : class
List<T> list = new List<T>();
foreach (object instance in _Instances)
var objType = instance.GetType();
if(type.IsAssignableFrom(objType))
list.Add(mapper(instance));
return list;
上面的实现中有几个类尚未定义,下面继续定义缺失的类.
IoC容器需要的其他类定义
/// IOC组件的Attribute
/// 当自定义的类上使用了该Attribute时,那么该类会被容器自动创建
[AttributeUsage(AttributeTargets.Class)]
public class IOCComponent : Attribute
/// 自动注入的Attribute
/// 标记了IOCComponent的类或通过IIOCContainer.InstanceAndInject、IIOCContainer.Inject
/// 创建的对象,其所有标记了Autowired的字段或属性会由IOC容器自动注入实例
[AttributeUsage(AttributeTargets.Field|AttributeTargets.Property)]
public class Autowired : Attribute
OK,依然如前所述,对于接触不多的人而言,该框架信息量确实比较大,请先放松.接下来通过实际使用的例子,再深入讲解上面的源码.
IoC框架使用示例
定义各种测试用Manager
日志管理类
[IOCComponent]
public class LogManager
private LogLevel _LogLevel = LogLevel.Debug;
public void Log(LogLevel level, string templte, params object[] args)
if (level < _LogLevel)
return;
string msg = args == null || args.Length == 0 ? templte : string.Format(templte, args);
msg = $"[level] Frame=Time.frameCount Time=Time.time -- msg";
switch (level)
case LogLevel.Debug:
case LogLevel.Info:
Debug.Log(msg);
break;
case LogLevel.Warning:
Debug.LogWarning(msg);
break;
case LogLevel.Exception:
Debug.LogException(new Exception(msg));
break;
case LogLevel.Error:
Debug.LogError(msg);
break;
public enum LogLevel
Debug,
Info,
Warning,
Exception,
Error
该类只是用于做简单的日志记录,会被后续其他Manager依赖使用.
注意到这个管理类上使用IOCComponent
这一Attribute
进行修饰.后续其他管理类也是如此.后续会解释为什么要这么做.
协程管理类
[IOCComponent]
public class CoroutineManager
private CoroutineRunner _CoroutineRunner;
public CoroutineManager()
var go = new GameObject("CoroutineRunner");
_CoroutineRunner = go.AddComponent<CoroutineRunner>();
GameObject.DontDestroyOnLoad(go);
public void StartCoroutine(IEnumerator enumerator)
_CoroutineRunner.StartCoroutine(enumerator);
public class CoroutineRunner : MonoBehaviour
该类只是用于简单的协程调用,会被后续其他Manager依赖使用
资源管理类
[IOCComponent]
public class AssetManager
[Autowired]
private CoroutineManager _CoroutineManager;
[Autowired]
private LogManager _LogManager;
public void LoadAsync<T>(string assetPath, Action<T> onLoaded) where T : Object
_CoroutineManager.StartCoroutine(_LoadAsync(assetPath, onLoaded));
private IEnumerator _LoadAsync<T>(string assetPath, Action<T> onLoaded) where T : Object
_LogManager.Log(LogLevel.Debug, "Loading 0", assetPath);
// Your load code here
// Now just wait for some seconds for demo
yield return new WaitForSeconds(3);
T loadedAsset = default(T);
_LogManager.Log(LogLevel.Debug, "Loaded 0 asset=1", assetPath, loadedAsset);
onLoaded?.Invoke(loadedAsset);
资源管理类,可以看到该类依赖了CoroutineManager
和LogManager
,但是没有对外提供这两个对象的设置.
GameObject管理类
[IOCComponent]
public class GameObjectManager
[Autowired]
private AssetManager _AssetManager;
[Autowired]
Unity-IOC