1.引言

Unity的生命周期是注册的类型对象的生命周期,而Unity默认情况下会自动帮我们维护好这些对象的生命周期,我们也可以显示配置对象的生命周期,Unity将按照配置自动管理,非常方便,下面就介绍一下 Unity中内置的生命周期管理器。

2.Lifetime Managers生命周期管理

准备以下类关系,用于演示

有2个接口类:IClass(班级接口)和ISubject(科目接口),其分别有2个实现类。看下面一个示例

public static void RegisterInstance()
{
IClass myClass = new MyClass();
IClass yourClass = new YourClass();
//为myClass实例注册默认实例
container.RegisterInstance<IClass>(myClass);
//为yourClass实例注册命名实例,同RegisterType
container.RegisterInstance<IClass>("yourInstance", yourClass);
container.Resolve</span>&lt;IClass&gt;<span style="color: #000000;">().ShowInfo();
container.Resolve</span>&lt;IClass&gt;(<span style="color: #800000;">"</span><span style="color: #800000;">yourInstance</span><span style="color: #800000;">"</span><span style="color: #000000;">).ShowInfo();

}

这段代码很简单,就是通过RegisterInstance方法为已存在的对象进行注册,这样可以通过UnityContainer来管理这些对象实例的生命周期。

需要注意的是,使用RegisterInstance来将已存在的 实例注册到UnityContainer中,默认情况下其实用的是ContainerControlledLifetimeManager,这个生命周期 是由UnityContainer来进行管理,UnityContainer会维护一个对象实例的强引用,当你将已存在的实例注册到 UnityContainer后,每次通过Resolve方法获取对象都是同一对象,也就是单件实例(singleton instance),具体有关生命周期相关信息在下面进行介绍。

由于RegisterInstance是对已存在的实例进行注册,所以无法通过配置文件来进行配置。有关RegisterInstance方法的其他重载我这边就不详细介绍了,可以点此查看详细的重载方法介绍。

下面将详细介绍各个Unity的生命周期管理

2.1 TransientLifetimeManager

瞬态生命周期,默认情况下,在使用RegisterType进行对象关系注册时如果没有指定生命周期管理器则默认使用这个生命周期管理器,这个生命周期管理器就如同其名字一样,当使用这种管理器的时候,每次通过ResolveResolveAll调用对象的时候都会重新创建一个新的对象。

需要注意的是,使用RegisterInstance对已存在的对象进行关系注册的时候无法指定这个生命周期,否则会报异常。

代码如下:

public static void TransientLifetimeManagerCode()
{
//以下2种注册效果是一样的
container.RegisterType<IClass, MyClass>();
container.RegisterType<IClass, MyClass>(new TransientLifetimeManager());
Console.WriteLine("-------TransientLifetimeManager Begin------");
Console.WriteLine("第一次调用RegisterType注册的对象HashCode:" +
container.Resolve<IClass>().GetHashCode());
Console.WriteLine("第二次调用RegisterType注册的对象HashCode:" +
container.Resolve<IClass>().GetHashCode());
Console.WriteLine("-------TransientLifetimeManager End------");
}

配置文件如下:

<register type="IClass" mapTo="MyClass">
<lifetime type="transient" />
<!--<lifetime type="SessionLifetimeManager"
value="Session#1" typeConverter="SessionLifetimeConverter" />-->
</register>

如果想在配置文件中在在注册关系的时候更改一个生命周期管理器只需在<register>配置节下新增<lifetime>既可(如果不新增则默认使用TransientLifetimeManager)。

其中<lifetime>有3个参数:

1)type,生命期周期管理器的类型,这边可以选择Unity内置的,也可以使用自定义的,其中内置的生命周期管理器会有智能提示。

2)typeConverter,生命周期管理器转换类,用户自定义一个生命周期管理器的时候所创建一个转换器。

3)value,初始化生命周期管理器的值。

配置文件读取代码:

public static void TransientLifetimeManagerConfiguration()
{
//获取指定名称的配置节
UnityConfigurationSection section = (UnityConfigurationSection)ConfigurationManager.GetSection("unity");
container.LoadConfiguration(section, "First");
Console.WriteLine(</span><span style="color: #800000;">"</span><span style="color: #800000;">-------TransientLifetimeManager Begin------</span><span style="color: #800000;">"</span><span style="color: #000000;">);
Console.WriteLine(</span><span style="color: #800000;">"</span><span style="color: #800000;">第一次调用RegisterType注册的对象HashCode:</span><span style="color: #800000;">"</span> +<span style="color: #000000;">
container.Resolve</span>&lt;IClass&gt;(<span style="color: #800000;">"</span><span style="color: #800000;">transient</span><span style="color: #800000;">"</span><span style="color: #000000;">).GetHashCode());
Console.WriteLine(</span><span style="color: #800000;">"</span><span style="color: #800000;">第二次调用RegisterType注册的对象HashCode:</span><span style="color: #800000;">"</span> +<span style="color: #000000;">
container.Resolve</span>&lt;IClass&gt;(<span style="color: #800000;">"</span><span style="color: #800000;">transient</span><span style="color: #800000;">"</span><span style="color: #000000;">).GetHashCode());
Console.WriteLine(</span><span style="color: #800000;">"</span><span style="color: #800000;">-------TransientLifetimeManager End------</span><span style="color: #800000;">"</span><span style="color: #000000;">);

}

效果图如下,可以看出每次产生的对象都是不同的:

2.2 ContainerControlledLifetimeManager

容器控制生命周期管理,这个生命周期管理器是RegisterInstance默认使用的生命周期管理器,也就是单件实例,UnityContainer会维护一个对象实例的强引用,每次调用的时候都会返回同一对象,示例代码如下:

public static void ContainerControlledLifetimeManagerCode()
{
IClass myClass = new MyClass();
//以下2种注册效果是一样的
container.RegisterInstance<IClass>("ccl", myClass);
container.RegisterInstance<IClass>("ccl", myClass, new ContainerControlledLifetimeManager());
container.RegisterType</span>&lt;IClass, MyClass&gt;(<span style="color: #0000ff;">new</span><span style="color: #000000;"> ContainerControlledLifetimeManager());
Console.WriteLine(</span><span style="color: #800000;">"</span><span style="color: #800000;">-------ContainerControlledLifetimeManager Begin------</span><span style="color: #800000;">"</span><span style="color: #000000;">);
Console.WriteLine(</span><span style="color: #800000;">"</span><span style="color: #800000;">第一次调用RegisterType注册的对象HashCode:</span><span style="color: #800000;">"</span> +<span style="color: #000000;">
container.Resolve</span>&lt;IClass&gt;<span style="color: #000000;">().GetHashCode());
Console.WriteLine(</span><span style="color: #800000;">"</span><span style="color: #800000;">第二次调用RegisterType注册的对象HashCode:</span><span style="color: #800000;">"</span> +<span style="color: #000000;">
container.Resolve</span>&lt;IClass&gt;<span style="color: #000000;">().GetHashCode());
Console.WriteLine(</span><span style="color: #800000;">"</span><span style="color: #800000;">第一次调用RegisterInstance注册的对象HashCode:</span><span style="color: #800000;">"</span> +<span style="color: #000000;">
container.Resolve</span>&lt;IClass&gt;(<span style="color: #800000;">"</span><span style="color: #800000;">ccl</span><span style="color: #800000;">"</span><span style="color: #000000;">).GetHashCode());
Console.WriteLine(</span><span style="color: #800000;">"</span><span style="color: #800000;">第二次调用RegisterInstance注册的对象HashCode:</span><span style="color: #800000;">"</span> +<span style="color: #000000;">
container.Resolve</span>&lt;IClass&gt;(<span style="color: #800000;">"</span><span style="color: #800000;">ccl</span><span style="color: #800000;">"</span><span style="color: #000000;">).GetHashCode());
Console.WriteLine(</span><span style="color: #800000;">"</span><span style="color: #800000;">-------ContainerControlledLifetimeManager End------</span><span style="color: #800000;">"</span><span style="color: #000000;">);

}

配置文件如下:

<register type="IClass" mapTo="MyClass" name="ccl">
<lifetime type="singleton" />
</register>

效果图如下,可以看出每次获取的对象都是同一对象:

2.3 HierarchicalLifetimeManager

分层生命周期管理器,这个管理器类似于ContainerControlledLifetimeManager,也是由UnityContainer来管理,也就是单件实例。不过与ContainerControlledLifetimeManager不 同的是,这个生命周期管理器是分层的,因为Unity的容器时可以嵌套的,所以这个生命周期管理器就是针对这种情况,当使用了这种生命周期管理器,父容器 和子容器所维护的对象的生命周期是由各自的容器来管理,代码如下(RegisterInstance情况也类似,这边就不展示了)

public static void HierarchicalLifetimeManagerCode()
{
container.RegisterType<IClass, MyClass>(new HierarchicalLifetimeManager());
//创建子容器
var childContainer = container.CreateChildContainer();
childContainer.RegisterType<IClass, MyClass>(new HierarchicalLifetimeManager());
Console.WriteLine(</span><span style="color: #800000;">"</span><span style="color: #800000;">-------ContainerControlledLifetimeManager Begin------</span><span style="color: #800000;">"</span><span style="color: #000000;">);
Console.WriteLine(</span><span style="color: #800000;">"</span><span style="color: #800000;">第一次调用父容器注册的对象HashCode:</span><span style="color: #800000;">"</span> +<span style="color: #000000;">
container.Resolve</span>&lt;IClass&gt;<span style="color: #000000;">().GetHashCode());
Console.WriteLine(</span><span style="color: #800000;">"</span><span style="color: #800000;">第二次调用父容器注册的对象HashCode:</span><span style="color: #800000;">"</span> +<span style="color: #000000;">
container.Resolve</span>&lt;IClass&gt;<span style="color: #000000;">().GetHashCode());
Console.WriteLine(</span><span style="color: #800000;">"</span><span style="color: #800000;">第一次调用子容器注册的对象HashCode:</span><span style="color: #800000;">"</span> +<span style="color: #000000;">
childContainer.Resolve</span>&lt;IClass&gt;<span style="color: #000000;">().GetHashCode());
Console.WriteLine(</span><span style="color: #800000;">"</span><span style="color: #800000;">第二次调用子容器注册的对象HashCode:</span><span style="color: #800000;">"</span> +<span style="color: #000000;">
childContainer.Resolve</span>&lt;IClass&gt;<span style="color: #000000;">().GetHashCode());
Console.WriteLine(</span><span style="color: #800000;">"</span><span style="color: #800000;">-------ContainerControlledLifetimeManager End------</span><span style="color: #800000;">"</span><span style="color: #000000;">);

}

由于配置文件不能配置这种层级效果,所以配置这种生命周期时只需要更改下生命周期名称:

<register type="IClass" mapTo="MyClass" name="hl">
<lifetime type="hierarchical" />
</register>

具体的效果图如下,可以看出父级和子级维护不同对象实例:

这边需要提一下的就是,Unity这种分级容器的好处就在于我们可以对于有不同生命周期的对象放在不同的容器中,如果一个子容器被释放,不会影响到其它子容器中的对象,但是如果根节点处父容器释放后,所有的子容器都将被释放。

2.4 PerResolveLifetimeManager

这个生命周期是为了解决循环引用而重复引用的生命周期,先看一下微软官方给出的实例:

public interface IPresenter
{ } public class MockPresenter : IPresenter

{

public IView View { get; set; }
</span><span style="color: #0000ff;">public</span><span style="color: #000000;"> MockPresenter(IView view)
{
View </span>=<span style="color: #000000;"> view;
}

}

public interface IView

{

IPresenter Presenter { get; set; }

}

public class View : IView

{

[Dependency]

public IPresenter Presenter { get; set; }

}

从这个例子中可以看出,有2个接口IPresenter和IView,还有2个类MockPresenter和View分别实现这2个接口,同时这2个类 中都包含了对另外一个类的对象属性,这个就是一个循环引用,而对应的这个生命周期管理就是针对这种情况而新增的,其类似于 TransientLifetimeManager,但是其不同在于,如果应用了这种生命周期管理器,则在第一调用的时候会创建一个新的对象,而再次通过 循环引用访问到的时候就会返回先前创建的对象实例(单件实例),代码如下:

public static void PerResolveLifetimeManagerCode()
{
var container = new UnityContainer()
.RegisterType<IPresenter, MockPresenter>()
.RegisterType<IView, View>(new PerResolveLifetimeManager());
</span><span style="color: #0000ff;">var</span> view = container.Resolve&lt;IView&gt;<span style="color: #000000;">();
</span><span style="color: #0000ff;">var</span> tempPresenter = container.Resolve&lt;IPresenter&gt;<span style="color: #000000;">();
</span><span style="color: #0000ff;">var</span> realPresenter =<span style="color: #000000;"> (MockPresenter)view.Presenter; Console.WriteLine(</span><span style="color: #800000;">"</span><span style="color: #800000;">-------PerResolveLifetimeManager Begin------</span><span style="color: #800000;">"</span><span style="color: #000000;">);
Console.WriteLine(</span><span style="color: #800000;">"</span><span style="color: #800000;">使用了PerResolveLifetimeManager的对象 Begin</span><span style="color: #800000;">"</span><span style="color: #000000;">);
Console.WriteLine(</span><span style="color: #800000;">"</span><span style="color: #800000;">通过Resolve方法获取的View对象:</span><span style="color: #800000;">"</span> +<span style="color: #000000;">
view.GetHashCode());
Console.WriteLine(</span><span style="color: #800000;">"</span><span style="color: #800000;">View对象中的Presenter对象所包含的View对象:</span><span style="color: #800000;">"</span> +<span style="color: #000000;">
realPresenter.View.GetHashCode());
Console.WriteLine(</span><span style="color: #800000;">"</span><span style="color: #800000;">使用了PerResolveLifetimeManager的对象 End</span><span style="color: #800000;">"</span><span style="color: #000000;">);
Console.WriteLine(</span><span style="color: #800000;">""</span><span style="color: #000000;">);
Console.WriteLine(</span><span style="color: #800000;">"</span><span style="color: #800000;">未使用PerResolveLifetimeManager的对象 Begin</span><span style="color: #800000;">"</span><span style="color: #000000;">);
Console.WriteLine(</span><span style="color: #800000;">"</span><span style="color: #800000;">View对象中的Presenter对象:</span><span style="color: #800000;">"</span> +<span style="color: #000000;">
realPresenter.GetHashCode());
Console.WriteLine(</span><span style="color: #800000;">"</span><span style="color: #800000;">通过Resolve方法获取的View对象:</span><span style="color: #800000;">"</span> +<span style="color: #000000;">
tempPresenter.GetHashCode());
Console.WriteLine(</span><span style="color: #800000;">"</span><span style="color: #800000;">未使用PerResolveLifetimeManager的对象 End</span><span style="color: #800000;">"</span><span style="color: #000000;">);
Console.WriteLine(</span><span style="color: #800000;">"</span><span style="color: #800000;">-------PerResolveLifetimeManager Begin------</span><span style="color: #800000;">"</span><span style="color: #000000;">);

}

从代码中可以看出,在注册对象的时候,仅对IView和View应用了PerResolveLifetimeManager,所以第二次访问View对象会返回同一实例。

具体配置文件如下,有关构造函数注入和属性注入的内容在下一篇文章中进行介绍

<alias alias="IPresenter" type="UnityStudyConsole.IPresenter, UnityStudyConsole" />
<alias alias="IView" type="UnityStudyConsole.IView, UnityStudyConsole" />
<alias alias="MockPresenter" type="UnityStudyConsole.MockPresenter, UnityStudyConsole" />
<alias alias="View" type="UnityStudyConsole.View, UnityStudyConsole" />
<container name="Second">
<register type="IPresenter" mapTo="MockPresenter">
<constructor>
<param name ="view" type="IView">
</param>
</constructor>
</register>
<register type="IView" mapTo="View" >
<lifetime type="perresolve"/>
<property name="Presenter" dependencyType="IPresenter"></property>
</register>
</container>

读取配置文件代码类似于前面其他配置文件读取代码,这里就不展示,具体请看示例代码。

具体的效果图如下:

可以看出2次调用View对象的HashCode都是一样的,而Presenter对象的HashCode不同。

2.5 PerThreadLifetimeManager

每线程生命周期管理器,就是保证每个线程返回同一实例,具体代码如下:

public static void PerThreadLifetimeManagerCode()
{
container.RegisterType<IClass, MyClass>(new PerThreadLifetimeManager());
var thread = new Thread(new ParameterizedThreadStart(Thread1));
Console.WriteLine("-------PerResolveLifetimeManager Begin------");
Console.WriteLine("默认线程 Begin");
Console.WriteLine("第一调用:" +
container.Resolve<IClass>().GetHashCode());
Console.WriteLine("第二调用:" +
container.Resolve<IClass>().GetHashCode());
Console.WriteLine("默认线程 End");
thread.Start(container);
}
public static void Thread1(object obj)
{
var tmpContainer = obj as UnityContainer;
Console.WriteLine("新建线程 Begin");
Console.WriteLine("第一调用:" +
tmpContainer.Resolve<IClass>().GetHashCode());
Console.WriteLine("第二调用:" +
tmpContainer.Resolve<IClass>().GetHashCode());
Console.WriteLine("新建线程 End"); Console.WriteLine("-------PerResolveLifetimeManager End------");

}

有关配置相关的代码与前面的生命周期管理器差不多,这边就不贴代码了,请看示例代码。

具体效果图如下:

同时需要注意的是,一般来说不建议在使用RegisterInstance对已存在的对象注册关系时使用PerThreadLifetimeManager,因为此时的对象已经在一个线程内创建了,如果再使用这个生命周期管理器,将无法保证其正确调用。

2.6 ExternallyControlledLifetimeManager

外部控制生命周期管理器,这个 生命周期管理允许你使用RegisterType和RegisterInstance来注册对象之间的关系,但是其只会对对象保留一个弱引用,其生命周期 交由外部控制,也就是意味着你可以将这个对象缓存或者销毁而不用在意UnityContainer,而当其他地方没有强引用这个对象时,其会被GC给销毁 掉。

在默认情况下,使用这个生命周期管理器,每次调用Resolve都会返回同一对象(单件实例),如果被GC回收后再次调用Resolve方法将会重新创建新的对象,示例代码如下:

public static void ExternallyControlledLifetimeManagerCode()
{
container.RegisterType<IClass, MyClass>(new ExternallyControlledLifetimeManager());
var myClass1 = container.Resolve<IClass>();
var myClass2 = container.Resolve<IClass>();
Console.WriteLine("-------ExternallyControlledLifetimeManager Begin------");
Console.WriteLine("第一次调用:" +
myClass1.GetHashCode());
Console.WriteLine("第二次调用:" +
myClass2.GetHashCode());
myClass1 = myClass2 = null;
GC.Collect();
Console.WriteLine("****GC回收过后****");
Console.WriteLine("第一次调用:" +
container.Resolve<IClass>().GetHashCode());
Console.WriteLine("第二次调用:" +
container.Resolve<IClass>().GetHashCode());
Console.WriteLine(</span><span style="color: #800000;">"</span><span style="color: #800000;">-------ExternallyControlledLifetimeManager End------</span><span style="color: #800000;">"</span><span style="color: #000000;">);

}

有关配置相关的代码与前面的生命周期管理器差不多,这边就不贴代码了,请看示例代码。

效果图如下:

3.小结

由于自己在学习Unity,在查找相关资料,在园子里找到这篇,看了一下还是很不错的,于是转载了,原文链接:http://www.cnblogs.com/kyo-yo/archive/2010/11/10/Learning-EntLib-Tenth-Decoupling-Your-System-Using-The-Unity-PART2-Learn-To-Use-Unity-Two.html,里面的文章写的蛮好的,大家可以查考。

  • 作者:Qlin
  • 出处:http://www.cnblogs.com/qqlin/
  • 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

[转载][IoC容器Unity]第二回:Lifetime Managers生命周期的更多相关文章

  1. [IoC容器Unity]第二回:Lifetime Managers生命周期

    1.引言 Unity的生命周期是注册的类型对象的生命周期,而Unity默认情况下会自动帮我们维护好这些对象的生命周期,我们也可以显示配置对象的生命周期,Unity将按照配置自动管理,非常方便,下面就介 ...

  2. [IoC容器Unity]第四回:使用范例

    1.引言 前面几个章节介绍了Unity的基本使用,主要分为程序和配置文件两种方法的使用,可以参考一下链接, [IoC容器Unity]第一回:Unity预览 [IoC容器Unity]第二回:Lifeti ...

  3. [IoC容器Unity]第三回:依赖注入

    1.引言 上节介绍了,Unity的Lifetime Managers生命周期,Unity具体实现依赖注入包含构造函数注入.属性注入.方法注入,所谓注入相当赋值,下面一个一个来介绍. 2.构造函数注入 ...

  4. IOC容器Unity的使用及独立配置文件Unity.Config

    [本段摘录自:IOC容器Unity 使用http://blog.csdn.net/gdjlc/article/details/8695266] 面向接口实现有很多好处,可以提供不同灵活的子类实现,增加 ...

  5. [IoC容器Unity] :Unity预览

    1.引言 高内聚,低耦合成为一个OO架构设计的一个参考标准.高内聚是一个模块或者一个类中成员跟这个模块或者类的关系尽量高,低耦合是不同模块或者不同类之间关系尽量简单. 拿咱国家举例来说,假如你是中国人 ...

  6. [IoC容器Unity]第一回:Unity预览

    1.引言 高内聚,低耦合成为一个OO架构设计的一个参考标准.高内聚是一个模块或者一个类中成员跟这个模块或者类的关系尽量高,低耦合是不同模块或者不同类之间关系尽量简单. 拿咱国家举例来说,假如你是中国人 ...

  7. Autofac容器对象实例的几种生命周期类型

    实例范围决定了如何在同一服务的请求之间共享实例. 请注意,您应该熟悉生命周期范围的概念,以便更好地理解此处发生的情况. 当请求服务时,Autofac可以返回单个实例(单实例作用域),新实例(每个依赖作 ...

  8. 微软IOC容器Unity简单代码示例3-基于约定的自动注册机制

    @(编程) [TOC] Unity在3.0之后,支持基于约定的自动注册机制Registration By Convention,本文简单介绍如何配置. 1. 通过Nuget下载Unity 版本号如下: ...

  9. 微软IOC容器Unity简单代码示例2-配置文件方式

    @(编程) 1. 通过Nuget下载Unity 这个就不介绍了 2. 接口代码 namespace UnityDemo { interface ILogIn { void Login(); } } n ...

随机推荐

  1. Windows环境下 PHP调用R脚本

    写在前面的: 由于是windows平台实现的,只要保证脚本命令能在cmd控制台运行,则可以在php中利用system()实现. 注意事项: (1).保证system的路径中无汉字和空格 !!  (亲身 ...

  2. Orleans部署

    一.配置指南 1,客户端配置 2,服务端配置 3,典型配置 4,配置.NET垃圾收集 5,SQL系统存储 二.监控 1,运行时监视 2,silo错误代码监测 3,客户端错误代码监测 三.解决部署问题 ...

  3. VB定义变量

    定义变量可以使用显式或隐式两种方式定义: 1.显式定义:Dim 变量名 As 类型 2.隐式定义:使用类型说明符 类型说明符如下: %——整型 &——长整型 !——单精度浮点数 #——双精度浮 ...

  4. 仙剑奇侠传 游戏 开发 教程 Xianjian qixia development Game development tutorial

    仙剑奇侠传 开发  游戏 开发 教程 Xianjian qixia development Game development tutorial 作者:韩梦飞沙 Author:han_meng_fei_ ...

  5. bzoj4237: 稻草人 cdq分治 单调栈

    目录 题目链接 题解 代码 题目链接 bzoj4237: 稻草人 题解 暴力统计是n^2的 考虑统计一段区间对另一端的贡献 对于y值cdq分治,降调一维 对于当前两个分治区间统计上面那部分对下面那部分 ...

  6. BZOJ.2199.[USACO2011 Jan]奶牛议会(2-SAT)

    题目链接 建边不说了.对于议案'?'的输出用拓扑不好判断,直接对每个议案的结果DFS,看是否会出现矛盾 Tarjan也用不到 //964kb 76ms #include <cstdio> ...

  7. HDU.3516.Tree Construction(DP 四边形不等式)

    题目链接 贴个教程: 四边形不等式学习笔记 \(Description\) 给出平面上的\(n\)个点,满足\(X_i\)严格单增,\(Y_i\)严格单减.以\(x\)轴和\(y\)轴正方向作边,使这 ...

  8. tomcat出现Error in dependencyCheck java.io.IOException: invalid manifest format

    我只能说这个错误很坑爹,检查了很多地方都没问题,结果最后在MANIFEST.MF 里面把所有的空的行都删掉就好了.坑爹有木有.

  9. 使用OClint进行iOS项目的静态代码扫描

    使用OClint进行iOS项目的静态代码扫描 原文链接:http://blog.yourtion.com/static-code-analysis-ios-using-oclint.html 最近需要 ...

  10. 使用Python学习selenium测试工具-4:查找元素

    转自:https://blog.csdn.net/wd168/article/details/51819930 web通常包含了Hyper Text Markup Language (HTML).Ca ...