iOS 依赖注入
iOS 依赖注入
最近读项目代码的总结!
什么是依赖呢,会有什么问题呢?平时写代码,这种依赖的方式太常见了。
看代码的时候发现,现实的代码还有这种a包含b,b包含a.然后a b中各自公开很多函数,然后相互各种调用,这是目前我们工程中存在的。
举个栗子 这是一个视频直播的界面Demo,这是一个真实存在的例子栗子
NS_ASSUME_NONNULL_BEGIN
@interface XXXLiveHouseController : UIViewController
/// 播放器界面
@property(strong, nonatomic) XXXLiveVideoController *playerController;
/// 弹幕界面
@property(strong, nonatomic) XXXRiverRunCommentController *riverRunCommentController;
///全屏的控制界面
@property(strong, nonatomic) XXXFullscreenVideoControl *fullscreenVideoControl;
/// 礼物展示界面
@property(strong, nonatomic) XXXGiftView *normalGiftView;
///
···这中间还有很多歌类似的属性
///
@end
NS_ASSUME_NONNULL_END
#endif /* LiveHouseController_h */
在<code>viewDidLoad</code>中,会对以上N个<code>property</code>进行 new,对property自己的属性也会进行相关设置,<code>viewDidload</code>大概有200 多行是创建property的、闭上眼睛感受一下。。
别笑,这个是很是存在的代码。
如果你想对某个property 进行单元测试, 对不起,臣妾做不到。 比如,<code>XXXFullscreenVideoControl</code>,全屏播放的界面上的挂件是很多的,比如,暂停,刷新,表情,输入框,等等等,已经大部分以来在这个界面了。
如果下一个版本的需求过来,想修改全屏播放的界面,基本也是在两个界面的代码之间进行搜索查找。然后看看哪里有依赖。上线之前完全靠强大的测试排除问题。
经历过好几个版本的迭代,依赖已经完全渗透在各个角落。
测试驱动写代码。写可以删除的代码,从学习依赖注入开始。
依赖,通俗易懂的讲,就是两个不同的对象之间产生了相互依赖。如果依赖过多,后期写测试用例,维护,改版都是很大的成本。
开始嘟嘟嘟概念,来自
概念内容来自git:here
依赖####
Java代码
如果在 Class A 中,有 Class B 的实例,则称 Class A 对 Class B 有一个依赖。例如下面类 Human 中用到一个 Father 对象,我们就说类 Human 对类 Father 有一个依赖。
public class Human {
...
Father father;
...
public Human() {
father = new Father();
}
}
仔细看这段代码我们会发现存在一些问题:
(1). 如果现在要改变 father 生成方式,如需要用new Father(String name)初始化 father,需要修改 Human 代码;
(2). 如果想测试不同 Father 对象对 Human 的影响很困难,因为 father 的初始化被写死在了 Human 的构造函数中;
(3). 如果new Father()过程非常缓慢,单测时我们希望用已经初始化好的 father 对象 Mock 掉这个过程也很困难。
依赖注入####
上面将依赖在构造函数中直接初始化是一种 Hard init 方式,弊端在于两个类不够独立,不方便测试。我们还有另外一种 Init 方式,如下:
public class Human {
...
Father father;
...
public Human(Father father) {
this.father = father;
}
}
上面代码中,我们将 father 对象作为构造函数的一个参数传入。在调用 Human 的构造方法之前外部就已经初始化好了 Father 对象。像这种非自己主动初始化依赖,而通过外部来传入依赖的方式,我们就称为依赖注入。
现在我们发现上面 1 中存在的两个问题都很好解决了,简单的说依赖注入主要有两个好处:
(1). 解耦,将依赖之间解耦。
(2). 因为已经解耦,所以方便做单元测试,尤其是 Mock 测试。
概念完毕
依赖注入有如下实现方式:
基于接口。实现特定接口以供外部容器注入所依赖类型的对象。
基于 set 方法。实现特定属性的public set方法,来让外部容器调用传入所依赖类型的对象。
基于构造函数。实现特定参数的构造函数,在新建对象时传入所依赖类型的对象。
基于注解。基于Java的注解功能,在私有变量前加“@Autowired”等注解,不需要显式的定义以上三种代码,便可以让外部容器传入对应的对象。该方案相当于定义了public的set方法,但是因为没有真正的set方法,从而不会为了实现依赖注入导致暴露了不该暴露的接口(因为set方法只想让容器访问来注入而并不希望其他依赖此类的对象访问)
在iOS中还可以基于 <code>xib</code>
举例子:
基于构造函数
@interface Car : NSObject
@property(nonatomic, strong) Engine *engine;
@property(nonatomic, strong) Brakes *brakes;
@end
@implementation Car
- (id)initWithEngine:(Engine *)engine brakes:(Brakes *)brakes {
self = [super init];
if (self) {
_engine = engine;
_brakes = brakes;
}
return self;
}
@end
基于<code>property</code>
Car *c = [Car new];
Engine *e = [Engine new];
e.name = @"Y";
c.engine = e;
基于工厂方法
Engine *create () {
//或许还有其他配置
return [Engine new];
}
Car *c = [Car new];
//或者其他地方调用工厂方法。总之工厂方法负责产出 Engine
c.engine = create();
基于<code>xib</code>
如图:
此处Car是继承自NSObject的类,所以xib也是解耦的神器。
你看,我们上面那个直播界面,完全可以向上面的依赖注入方式整理的很清楚,我的习惯,如果<code>property</code>的生成方式只是 <code>new</code>的方式,那么就是用<code>xib</code>进行注入,如果<code>property</code>比较少,就通过构造函数进行注入,如果多了,并且不会经常改动,就通过set方法注入,批量用到的就通过 工厂方法生成。
个人喜好,最近喜欢通过类簇来分解几千行代码的类。
当然,这些第三库都有做的很好的。objection 就是其中一个依赖注入很好的库
用法如下:
@class Engine, Brakes;
@interface Car : NSObject
// 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;
@end
@implementation Car
objection_requires(@"engine", @"brakes")
@synthesize engine, brakes, awake;
- (id)init {
self = [super init];
if (self) {
}
return self;
}
@end
其他地方就可以这样子调用
JSObjectionInjector *injector = [JSObjection createInjector];
Car *c = [injector getObject:[Car class]];
objection的栗子来自<code>objection</code> git网址,还有很多注入的用法,感兴趣的可以自己查看git.。
至于开篇说的相互包含的问题,我觉得代理就能完美的解决。
说了这么多,就是为了立帖为证,下回督促自己分析 objection 源码