iOS项目依赖注入简介

依赖注入(Dependency Injection)

依赖注入 最大的特点就是:帮助我们开发出松散耦合(loose coupled)、可维护、可测试的代码和程序。这条原则的做法是大家熟知的面向接口,或者说是面向抽象编程。 众所周知该编程思想在各大语言中都有体现如jave、 C++、 PHP 以及 .net中。当然设计模式的广泛程度远远大于这些,iOS 当然也不例外。 本文主要介绍本人在学习Dependency Injection的时候的学习过程以及对一些学习资料的总结,主要介绍iOS中的两大框架Objection 和 Typhoon 。 在 Android上比较流行的有 RoboGuice 和 Dagger 等.

什么是依赖注入(Dependency Injection)? 

依赖注入(Dependency Injection) 是一个将行为从依赖中分离的技术,简单地说,它允许开发者定义一个方法函数依赖于外部其他各种交互,而不需要编码如何获得这些外部交互的实例。 这样就在各种组件之间解耦,从而获得干净的代码,相比依赖的硬编码, 一个组件只有在运行时才调用其所需要的其他组件,因此在代码运行时,通过特定的框架或容器,将其所需要的其他依赖组件进行注入,主动推入。

依赖注入是最早SpringPiconcontainer等提出,如今已经是一个缺省主流模式,并扩展到前端如Angular.js等等。

1. 依赖

如果在 Class A中,有 Class B的实例,则称 Class A对 Class B 有一个依赖。例如下面类 ViewControllerA 中用到一个 ViewControllerB 对象,我们就说类 ViewControllerA 对类 ViewControllerB 有一个依赖。

 #import "ViewControllerB.h"

@implementation ViewControllerA

- (void)buttonTapped{
    ViewControllerB *vc = [[ViewControllerB alloc] init];
    [self.navigationController pushViewController:vc animated:YES];
}
 

仔细看这段代码我们会发现存在一些问题:

(1). 如果现在要改变 ViewControllerB 生成方式,如需要用initWithOrderid:(NSString * orderid)初始化 vc,需要修改 ViewControllerA 代码;

(2). 如果想测试不同 ViewControllerB 对象对 ViewControllerA 的影响很困难,因为 ViewControllerB 的初始化被写死在了ViewControllerA` 的构造函数中;

(3). 如果[[ViewControllerB alloc] init]过程非常缓慢,单测时我们希望用已经初始化好的 ViewControllerB 对象 Mock 掉这个过程也很困难。

2. 依赖注入

上面将依赖在构造函数中直接初始化是一种 Hard init 方式,弊端在于两个类不够独立,不方便测试。我们还有另外一种 Init 方式,如下:

@interface ViewControllerA ()  

@property (nonatomic, readonly) ViewControllerB *vcB;  

@end  

@implementation ViewControllerA  

// vcB是在ViewControllerA被创建之前被创建的并且作为参数传进来,
// 调用者如果想,还可以自定义。
- (instancetype)initWithEngine:(ViewControllerB *)vcB
{
   ...  

   _vcB = vcB;  

   return self;
}  

@end

上面代码中,我们将 vcB 对象作为构造函数的一个参数传入。在调用 ViewControllerA 的构造方法之前外部就已经初始化好了 vcB 对象。像这种非自己主动初始化依赖,而通过外部来传入依赖的方式,我们就称为依赖注入

现在我们发现上面 1中存在的两个问题都很好解决了,简单的说依赖注入主要有两个好处:

  • 解耦,将依赖之间解耦。
  • 因为已经解耦,所以方便做单元测试,尤其是 Mock测试。

那么问题来了,如何学习Dependency Injection呢 ?iOS有关DI依赖注入的框架比较好用的有两个:Objection 和 Typhoon.下面就从几个方便来介绍下这两个框架

一:Objection 和 Typhoon这两个框架有什么区别呢 其实这两个框架各有优势:

  1. Objection框架,使用起来比较灵活,用法比较简单。示例代码如下:

属性注册:

@class Engine, Brakes;

@interface Car : NSObject
{
     Engine *engine;
     Brakes *brakes;
     BOOL awake;
}

// Will be filled in by objection
@property(nonatomic, strong) Engine *engine;
// Will be filled in by objection
@property(nonatomic, strong) Brakes *brakes;
@property(nonatomic) BOOL awake;

@implementation Car
objection_requires(@"engine", @"brakes") //属性的依赖注入
@synthesize engine, brakes, awake;
@end

方法注入:

@implementation Truck
objection_requires(@"engine", @"brakes")
objection_initializer(truckWithMake:model:)//方法的依赖注入
+ (instancetype)truckWithMake:(NSString *) make model: (NSString *)model {
  ...
}
@end

2.对比来说Typhoon的使用起来就比较规范,首先需要创建一个TyphoonAssembly的子类。其需要注入的方法和属性都需要写在这个统一个子类中,当然可以实现不同的子类来完成不同的功能:

@interface MiddleAgesAssembly : TyphoonAssembly

- (Knight*)basicKnight;

- (Knight*)cavalryMan;

- (id<Quest>)defaultQuest;

@end

属性注入:

- (Knight *)cavalryMan
{
    return [TyphoonDefinition withClass:[CavalryMan class]
    configuration:^(TyphoonDefinition *definition) {

    [definition injectProperty:@selector(quest) with:[self defaultQuest]];
    [definition injectProperty:@selector(damselsRescued) with:@(12)];
}];
}

方法注入:

- (Knight *)knightWithMethodInjection
{
        return [TyphoonDefinition withClass:[Knight class]
    configuration:^(TyphoonDefinition *definition) {
    [definition injectMethod:@selector(setQuest:andDamselsRescued:)
        parameters:^(TyphoonMethod *method) {

        [method injectParameterWith:[self defaultQuest]];
        [method injectParameterWith:@321];
    }];
}];
}

3.当然还有一些硬性的区别就是Typhoon现在已经支持Swift

4.两者维护时间都超过2年以上。

Tythoon官方介绍的优势:

1)Non-invasive. No macros or XML required. Uses powerful ObjC runtime instrumentation.

2)No magic strings – supports IDE refactoring, code-completion and compile-time checking.

3)Provides full-modularization and encapsulation of configuration details. Let your architecture tell a story.

4)Dependencies declared in any order. (The order that makes sense to humans).

5)Makes it easy to have multiple configurations of the same base-class or protocol.

 6)Supports injection of view controllers and storyboard integration. Supports both initializer and property injection, plus life-cycle management.

7)Powerful memory management features. Provides pre-configured objects, without the memory overhead of singletons.

8)Excellent support for circular dependencies.

9)Lean. Has a very low footprint, so is appropriate for CPU and memory constrained devices.

10)While being feature-packed, Typhoon weighs-in at just 3000 lines of code in total.

11)Battle-tested — used in all kinds of Appstore-featured apps.

大体翻译过来:

1)非侵入性。不需要宏或XML。使用强大的ObjC运行时仪器。
2)没有魔法字符串——支持IDE重构,完成和编译时检查。
3)提供full-modularization和封装的配置细节。让你的架构告诉一个故事。
4)依赖关系中声明的任何顺序。(对人类有意义的顺序)。
5)很容易有多个配置相同的基类或协议。
6)支持注射的视图控制器和故事板集成。同时支持初始化器和属性注入,以及生命周期管理。
7)强大的内存管理功能。提供预配置对象,没有单件的内存开销。
8)优秀的支持循环依赖。
9)精益。占用很低,所以适合CPU和内存受限的设备。
10),功能强大,台风重总共只有3000行代码。
11)久经沙场,用于各种Appstore-featured应用。

 针对这两个框架网上教程并不多,收集了一些比较有用的资料。最主要的用法还得看官方文档分别在:Objection 和 Typhoon 
 

objc.io官网的博文 Dependency Injection 和 Typhoon原创大神(Graham Lee)的文章 Dependency Injection, iOS and You 不看后悔一辈子^_^

Objection 是一个轻量级的依赖注入框架,受Guice的启发,Google Wallet 也是使用的该项目。「依赖注入」是面向对象编程的一种设计模式,用来减少代码之间的耦合度。通常基于接口来实现,也就是说不需要new一个对象,而是通过相关的控制器来获取对象。2013年最火的PHP框架 laravel 就是其中的典型。

假设有以下场景:ViewControllerA.view里有一个button,点击之后push一个ViewControllerB,最简单的写法类似这样:

-  (void)viewDidLoad
{
    [super viewDidLoad];
    UIButton *button = [UIButton buttonWithType:UIButtonTypeSystem];
    button.frame = CGRectMake(100, 100, 100, 30);
    [button setTitle:@"Button" forState:UIControlStateNormal];
    [button addTarget:self action:@selector(buttonTapped) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:button];
}

- (void)buttonTapped
{
    ViewControllerB *vc = [[ViewControllerB alloc] init];
    [self.navigationController pushViewController:vc animated:YES];
}
 

这样写的一个问题是,ViewControllerA需要import ViewControllerB,也就是对ViewControllerB产生了依赖。依赖的东西越多,维护起来就越麻烦,也容易出现循环依赖的问题,而objection正好可以处理这些问题。

实现方法是:先定义一个协议(protocol),然后通过objection来注册这个协议对应的class,需要的时候,可以获取该协议对应的object。对于使用方无需关心到底使用的是哪个Class,反正该有的方法、属性都有了(在协议中指定)。这样就去除了对某个特定Class的依赖。也就是通常所说的「面向接口编程」

JSObjectionInjector *injector = [JSObjection defaultInjector]; // [1]
UIViewController <ViewControllerAProtocol> *vc = [injector getObject:@protocol(ViewControllerAProtocol)]; // [2]
vc.backgroundColor = [UIColor lightGrayColor]; // [3]
UINavigationController *nc = [[UINavigationController alloc] initWithRootViewController:vc];
self.window.rootViewController = nc;
  • [1] 获取默认的injector,这个injector已经注册过ViewControllerAProtocol了。
  • [2] 获取ViewControllerAProtocol对应的Object
  • [3] 拿到VC后,设置它的某些属性,比如这里的backgroundColor,因为在ViewControllerAProtocol里有定义这个属性,所以不会有warning

可以看到这里没有引用ViewControllerA。再来看看这个ViewControllerAProtocol是如何注册到injector中的,这里涉及到了Module,对Protocol的注册都是在Module中完成的。Module只要继承JSObjectionModule这个Class即可。

@interface ViewControllerAModule : JSObjectionModule
@end

@implementation ViewControllerAModule

+ (void)load{
    JSObjectionInjector *injector = [JSObjection defaultInjector];
    injector = injector ? : [JSObjection createInjector];
    injector = [injector withModule:[[ViewControllerAModule alloc] init]];
    [JSObjection setDefaultInjector:injector];
}

- (void)configure
{
    [self bindClass:[ViewControllerA class] toProtocol:@protocol(ViewControllerAProtocol)];
}
@end

绑定操作是在configure方法里进行的,这个方法在被添加到injector里时会被自动触发。

JSObjectionInjector *injector = [JSObjection defaultInjector]; // [1]
injector = injector ? : [JSObjection createInjector]; // [2]
injector = [injector withModule:[[ViewControllerAModule alloc] init]]; // [3]
[JSObjection setDefaultInjector:injector]; // [4]
  • [1] 获取默认的injector
  • [2] 如果默认的 injector 不存在,就新建一个
  • [3] 往这个 injector 里注册我们的 Module
  • [4] 设置该 injector 为默认的 injector

这段代码可以直接放到 + (void)load里执行,这样就可以避免在AppDelegateimport各种Module

因为我们无法直接获得对应的Class,所以必须要在协议里定义好对外暴露的方法和属性,然后该Class也要实现该协议。

@protocol ViewControllerAProtocol <NSObject>
@property (nonatomic) NSUInteger currentIndex;
@property (nonatomic) UIColor *backgroundColor;
@end

@interface ViewControllerA : UIViewController <ViewControllerAProtocol>
@end

通过Objection实现依赖注入后,就能更好地实现SRP(Single Responsibility Principle),代码更简洁,心情更舒畅,生活更美好。

总体来说,这个lib还是挺靠谱的,已经维护了两年多,也有一些项目在用,对于提高开发成员的效率也会有不少的帮助,可以考虑尝试下

时间: 2024-08-28 11:03:39

iOS项目依赖注入简介的相关文章

Asp.Net Core 3.1 Api 集成Abp项目依赖注入

Abp 框架 地址https://aspnetboilerplate.com/ 我们下面来看如何在自己的项目中集成abp的功能 我们新建core 3.1 API项目和一个core类库 然后 两个项目都要安装Abp Nuget Package 版本为5.1.0 如上图,在Application项目新建项目模块类,Initialize方法中,会在启动时扫描dll中需要依赖注入的类和接口 如上图,在ApiHost项目新建项目模块类,该项目依赖Application项目 在Application 建立Q

Dotnet Core依赖注入

1.依赖注入简介 依赖是指一个对象需要另一个对象,在下面到例子中,MyDependency类中存在方法WriteMessage方法,该方法被别的方法所使用: 1 public class MyDependency 2 { 3 public MyDependency() 4 { 5 } 6 7 public Task WriteMessage(string message) 8 { 9 Console.WriteLine( 10 $"MyDependency.WriteMessage called

NET Core,你必须了解无处不在的“依赖注入”

NET Core,你必须了解无处不在的“依赖注入” ASP.NET Core的核心是通过一个Server和若干注册的Middleware构成的管道,不论是管道自身的构建,还是Server和Middleware自身的实现,以及构建在这个管道的应用,都需要相应的服务提供支持,ASP.NET Core自身提供了一个DI容器来实现针对服务的注册和消费.换句话说,不只是ASP.NET Core底层框架使用的服务是由这个DI容器来注册和提供,应用级别的服务的注册和提供也需要以来这个DI容器,所以正如本文标题

学习ASP.NET Core,你必须了解无处不在的“依赖注入”

ASP.NET Core的核心是通过一个Server和若干注册的Middleware构成的管道,不论是管道自身的构建,还是Server和Middleware自身的实现,以及构建在这个管道的应用,都需要相应的服务提供支持,ASP.NET Core自身提供了一个DI容器来实现针对服务的注册和消费.换句话说,不只是ASP.NET Core底层框架使用的服务是由这个DI容器来注册和提供,应用级别的服务的注册和提供也需要以来这个DI容器,所以正如本文标题所说的——学习ASP.NET Core,你必须了解无

【无私分享:ASP.NET CORE 项目实战(第二章)】添加EF上下文对象,添加接口、实现类以及无处不在的依赖注入(DI)

目录索引 [无私分享:ASP.NET CORE 项目实战]目录索引 简介 上一章,我们介绍了安装和新建控制器.视图,这一章我们来创建个数据模型,并且添加接口和实现类. 添加EF上下文对象 按照我们以前的习惯,我们还是新建几个文件夹 Commons:存放帮助类 Domians:数据模型 Services:接口和实现类 我们在Domains文件夹下添加一个类库 Domain 我们新建一个类 ApplicationDbContext 继承 DbContext 1 using Microsoft.Ent

iOS依赖注入框架系列(一):介绍Typhoon

介绍 作为这一系列的一部分,我不会去考虑依赖性倒置原则,或模式依赖注入的理论 -理所当然地认为读者已经做好充分准备,以确保了解禅宗,并直接进入实践(链接探索中给出的理论帖子的结尾). 台风框架 -是最有名的和流行的执行DI容器Objective-C的应用和斯威夫特. 该项目是相当年轻的-第一次提交被做在2012年底,但它已得到了很多球迷 . 特别值得一提它的创始人的积极支持该项目(其中一个,顺便说一句,生活和在鄂木斯克作品) -满足最成熟的问题十分钟,几个小时来讨论整个团队加入. 为什么我们需要

iOS依赖注入框架系列(二):设置Typhoon

在循环的最后一部分,我们遇到了依赖注入框架为iOS - 台风,并审查其项目Rambler.Pochta使用的基本示例. 这一次,我们进入它的内部结构的研究. 周期"在iOS中右依赖驱动的应用程序" 熟悉台风 该器件台风 模块化台风 台风技巧与诀窍 台风替代品 (可选)Rambler.iOS#3. 依赖注入的iOS. 幻灯片 (可选)Rambler.iOS#3. 依赖注入的iOS. 视频 介绍 要开始分析一个小字典将被广泛使用在这篇文章中的术语: 大会(读作[essembli]). 最近

iOS依赖注入框架系列(三):模块化Typhoon

在以前的文章 系列中,我们简要地讨论了组织和运作框架台风的基本原则 -为iOS依赖注入容器. 但是,如何将工具布置一点理解-最重要的是要正确地使用它. 在第一部分中,我们看的创建依赖关系,现在处理更高级别的配置设置的各种例子-模块分为TyphoonAssembly自己和测试. 周期"在iOS中右依赖驱动的应用程序" 熟悉台风 该器件台风 模块化台风 台风技巧与诀窍 台风替代品 (可选)Rambler.iOS#3. 依赖注入的iOS. 幻灯片 (可选)Rambler.iOS#3. 依赖注

iOS开发笔记--使用CocoaPods来管理iOS项目的依赖库

原文地址:http://blog.devdong.com/blog/2013/12/28/shi-yong-cocoapodslai-guan-li-iosxiang-mu-de-yi-lai-ku/ 前言 细细算来,我接触iOS已经有1.5f年的时间了,虽然其中有差不多一年的时间是在大四经历自学和实习的这个阶段.抛去那段时间不算,毕业后在现在的公司工作差不多半年了… 在经历过的几个项目上基本上每一个都会用到第三方开源库,比如SDWebImage.AFNetworking.MBProgressH