Created by Linxi 2020/05/01

Dependency injection

First, what is dependency injection

For example, if AController jumps to BController, then BController needs to be instantiated within AController as follows

@implementation AController : UIViewController

...

- (void)jump 
{
	BController *bController = [[BController alloc] init];
    [self.navigationController pushViewController:bController animated:YES];
}

@end

Copy the code

In this case, when the AController is wrapped as a component, the configuration of the BController will be limited. No details of the BController can be changed externally, so we will modify it slightly

@implementation AController : UIViewController ... - (instancetype)initWithCreateBlock:(UIViewController *(^)(void))createBViewControllerBlock { .... self.createBViewControllerBlock = createBViewControllerBlock; . } - (void)jump { UIViewController *bController = self.createBViewControllerBlock(); [self.navigationController pushViewController:bController animated:YES]; } @endCopy the code
[[AController alloc] initWithCreateBlock:UIViewController* ^{
	BController *bController = [[BController alloc] initWithTitle:@"xxx"];
	return bController;
}];
Copy the code

If the creation of the BController is exposed through a Block, the internal AController does not care how the BController was created, then the dependency of AController on the BController will be injected through an external Block.

This is dependency injection.

Of course this is the simplest dependency injection and can’t meet our complex needs, so sometimes we need to use third party frameworks such as Object and Typhoon

Objection

Next, illustrate the use of objects

Object is a dependency injection framework that allows properties inside a class to be instantiated when you obtain an instance of the class. Here’s an example:

//Car.h

@class Engine,Break;

@interface Car : NSObject

@property (nonatomic, strong) Engine *engine;

@property (nonatomic, strong) Break *breaks;

@end

Copy the code
//Car.m

#import <Objection/Objection.h>

@implementation Car
objection_requires(@"engine"The @"breaks")

@end

Copy the code

Create a default syringe

JSObjectionInjector *injector = [JSObjection createInjector];
[JSObjection setDefaultInjector:injector];
Copy the code

Instantiate the Car object

Car *car = [[JSObjection defaultInjector] getObject:[Car class]];
Copy the code

The engine object and breaks object that you depend on are instantiated through the init method

Print the property last

car <Car: 0x6000006d8480> engine <Engine: 0x6000004841b0> breaks <Break: 0x6000004841e0>
Copy the code

If the Car object cannot be instantiated using a custom constructor such as init or initWithXXX, then we need to specify a method in which the syringe builds the dependency

@implementation Car
objection_requires(@"engine"The @"breaks")
- (void)awakeFromNib {
  [[JSObjection defaultInjector] injectDependencies:self];
}
@end
Copy the code

After the Car has been initialized by the syringe, the -awakeFromobjection method is called, where additional values can be assigned

- (void)awakeFromObjection 
{
	self.test = @"111";
}
Copy the code

All of the above are just init objects, but more often we need to specify the constructor

@implementation Car objection_Initializer_sel (@selector(initWithObject)) // This macro only needs to occur once - (instancetype)initWithObject:(id)object {if (self = [super init]) {
        self.test = object;
    }
    return self;
}
@end
Copy the code

ArgumentList: argumentList

 Car *car = [[JSObjection defaultInjector] getObject:[Car class] argumentList:@[@"aaaa"]].Copy the code

Or if you don’t want to write the objection_initializer_sel() macro you can just change it in the fetching method to

Car *car = [[JSObjection defaultInjector] getObject:[Car class] initializer:@selector(initWithObject:) argumentList:@[@"aaaa"]].Copy the code

The effect is the same

Object factory

Add an object factory property to Car

@property(nonatomic, strong) JSObjectFactory *objectFactory;
Copy the code

Then the tag injection adds an extra objectFactory to it

objection_requires(@"engine"The @"breaks"The @"objectFactory")
Copy the code

Then you can go through

id obj = [self.objectFactory getObject:[Engine class]];
Copy the code

Get the corresponding object

The module

You can create a module that inherits from the JSObjectionModule, bind the corresponding things in it, and get the corresponding values directly

For example, a protocol and a module class, the object binds the class name to the protocol that the class follows


@protocol APIService <NSObject>

- (void)api:(NSString *)params;

@end


@interface ModuleA : JSObjectionModule

@end

@implementation ModuleA

- (void)configure
{
    [self bindClass:[MyAPIService class] toProtocol:@protocol(APIService)];
}

@end

Copy the code

At this point, the syringe initialization mode is changed to

JSObjectionInjector *injectorA = [JSObjection createInjector:ModuleA.new]; [JSObjection setDefaultInjector:injectorA];
Copy the code

Instead of going through ModuleA instance objects, you can get the corresponding objects that follow the protocol

MyAPIService *delegate = [injectorA getObject:@protocol(APIService)];
Copy the code

Note that since the binding is done using the bindClass: method, different objects are fetched each time

In addition to binding object class names and protocols, you can bind an object and bind a class name

@implementation ModuleA

- (void)configure
{
    [self bindToClass :[UIApplication Class]]; [selfbindToProtocol: @Protocol (UIApplicationDelegate)]; } @endCopy the code

** Note that the same object is fetched each time since the bind: method was used

When an object is created, you can intervene with the bindBlock: method

@implementation ModuleA

- (void)configure
{
    [self bindClass:[MyAPIService class] toProtocol:@protocol(APIService)];
    [self bindBlock:^id(JSObjectionInjector *context) {
        MyAPIService *service = [[MyAPIService alloc] init];
        service.buildByMySelf = YES;
        return service;
    } toClass:[MyAPIService class]];
}

@end
Copy the code

The example above shows that MyAPIService is instantiated with buildByMySelf = YES

But with this method, if we take an object out with a syringe and we have parameters, we can’t get the parameters, so we need to use the ObjectionProvider protocol

@interface ProviderA : JSObjectionModule<JSObjectionProvider>

@end

@implementation ProviderA
- (id)provide:(JSObjectionInjector *)context arguments:(NSArray *)arguments
{
    MyAPIService *service = [[MyAPIService alloc] init];
    service.buildByProvider = YES;
    service.arguments = arguments;
    return service;
}

- (void)configure
{
	[self bindProvider:[ModuleA new] toClass:MyAPIService.class];
}

@end
Copy the code

This way you can manually build the object and get the parameters

scope

BindClass:, bindBlock:, and bindProvider: all have an extension parameter inScope:(JSObjectionScope)scope;

Such as:

[self bindClass:[MyAPIService class] toProtocol:@protocol(APIService) inScope:JSObjectionScopeSingleton named:@""];

[self bindBlock:^id(JSObjectionInjector *context) {
	MyAPIService *service = [[MyAPIService alloc] init];
	service.buildByMySelf = YES;
	return service;
} toClass:[MyAPIService class] inScope:JSObjectionScopeSingleton named:@""];

[self bindProvider:[ModuleA new] toClass:MyAPIService.class inScope:JSObjectionScopeSingleton];
Copy the code

JSObjectionScopeSingleton means the syringe out of is all the same object, JSObjectionScopeNormal means that the syringe out different objects.

conclusion

To implement dependency injection, there are only two things you need to do: configure dependencies and get dependent objects. When configuring dependencies, you can use several commonly used macros to quickly configure dependencies. You can also use the concept of modules to do more binding, which allows you to bind certain classes or protocol interfaces to certain objects or classes of objects. You can use the key Injector to get the objects you need.