Original link: https://corner.squareup.com/2015/06/dependency-injection-in-objc.html
This article is your own by reading Dependency injection:give Your IOS Code a Shot in the ARM for summary + translation, there are errors please do not mean to point out. Here is the text:
Dependency injection can pass the required parameters through an initialization method (or constructor), or through a property (setter). Both of these methods are explained here.
Initialize Method injection:
-(Instancetype) InitWithDependency1: (Dependency1 *) d1 dependency2: (Dependency2 *) D2;
Attribute injection:
@property (nonatomic, retain) Dependency1 *dependency1; @property (nonatomic, retain) Dependency2 *dependency2;
It is generally more inclined to initialize the injection, which is injected through the property if there is no way to inject it when initializing (the constructor). In the case of initialization injection, these dependencies may still need to exist as attributes, but these properties should be set to read-only (ReadOnly).
Why use Dependency Injection
several important points of dependency injection:1. A clear definition. Using dependency injection clearly tells the user what needs to be done to manipulate the class object, what variables to initialize, and can hide some dangerous dependencies, such as global variables;2. Use the composition. That is, we have been adhering to the principle of less inheritance. It can improve the reusability of code;3. More simple customization. It is easier to customize when creating an object by passing in the specified parameters. 4. A clear owner. 5. Testability. Because we only need to follow the initialization method, passing in the required parameters to operate, do not need to manage the hidden dependencies.
start using Dependency injectionfirst, the type of injection class First, we divide classes (class) into two categories: the class is simple and the class is complex. The simple class is one that has no dependencies or simply relies on other simple classes, and this simple class is impossible to subclass, assuming that their functionality is unambiguous and immutable, and does not refer to additional resources. In the cocoa framework there are many simple classes, such as: NSString, Nsarray, NSNumber, nsdictionary, etc. complex classes are the opposite. They have other complex dependencies, including application level logic (which can vary depending on the logic of the application). Or they need to access other external resources, such as hard disks, networks, or global variables. These classes will become complex in your application, and they may contain all the controller objects or all of the model objects. The complex classes in the Cocoa Framework are: nsurlconnection, Uiviewcontroller, etc... once categorized, we can easily select which complex classes are in the application and start optimizing them.
second, in the initialization of the dependency allocationlet's start with an example. Original code:
@interface Rcracecar () @property (nonatomic, readonly) rcengine *engine; @end @implementation rcracecar-(instancetype) init{... Create an engine. Note: This cannot be customized or unmodified Rcracecar internal cannot be simulated _engine = [[Rcengine alloc] init]; return self;} @end
after using the dependency injection facelift:
@interface Rcracecar () @property (nonatomic, readonly) rcengine *engine; @end @implementation rcracecar// The engine is created before the car is created and is passed in as a parameter, and/or the caller can customize it if they wish. -(Instancetype) Initwithengine: (Rcengine *) engine{ ... _engine = engine; return self;} @end
third, delayed initialization of dependencyin general, there are some objects that need to be used after initialization, and sometimes almost impossible, such as in a user's favorite list, when a collection is not a time to display a harmonious page, but this situation is sometimes rarely encountered, that as long as the user collects a resource, This page is not needed. Examples of fire extinguishers are as follows:
@interface Rcracecar () @property (nonatomic) rcengine *engine; @end @implementation rcracecar-(instancetype) Initwithengine: (Rcengine *) engine{... _engine = engine; return self;} -(void) recoverfromcrash{ if (self.fire! = nil) { Rcfireextinguisher *fireextinguisher = [[Rcfireextinguisher ALLOC] init]; [Fireextinguisher ExtinguishFire:self.fire];} } @end
in this case, the car certainly wants to always be okay, so we may never need a fire extinguisher. Because the odds of using this fire extinguisher object are low, we don't want to make each car slow to create it directly from the initialization method . Or, if our car needs to be recovered for multiple car crashes, this will require the creation of multiple fire extinguishers. In this case, we can use a factory method.
The factory method is a standard objective-c block that requires no parameters and returns a specific instance object. When an object relies on the use of this block, it does not need to know exactly how it was created.
below, a fire extinguisher is created from a factory using dependency injection.
typedef rcfireextinguisher * (^rcfireextinguisherfactory) (); @interface Rcracecar () @property (nonatomic, ReadOnly) Rcengine *engine; @property (nonatomic, copy, readonly) rcfireextinguisherfactory fireextinguisherfactory;@ End@implementation rcracecar-(Instancetype) Initwithengine: (Rcengine *) engine fireextinguisherfactory: ( Rcfireextinguisherfactory) extfactory{... _engine = engine; _fireextinguisherfactory = [extfactory copy]; return self;} -(void) recoverfromcrash{ if (self.fire! = nil) { Rcfireextinguisher *fireextinguisher = Self.fireextinguisherfactory (); [Fireextinguisher ExtinguishFire:self.fire];} } @end
The factory is also useful under the following conditions. When we need to create a dependency that does not know the number, it is created even after it is known to be initialized. as follows:
@implementation rcracecar-(Instancetype) Initwithengine: (Rcengine *) engine Transmission: (Rctransmission *) Transmission wheelfactory: (Rcwheel * (^) ()) Wheelfactory; {Self = [super init]; if (self = = nil) { return nil; } _engine = engine; _transmission = transmission; _leftfrontwheel = Wheelfactory (); _leftrearwheel = Wheelfactory (); _rightfrontwheel = Wheelfactory (); _rightrearwheel = Wheelfactory (); The wheel factory is retained and a spare tire is then required. _wheelfactory = [wheelfactory copy]; return self;} @end
Avoid redundant configuration
If an object should not be configured inside other objects, it is a convenience constructor (e.g. + [Nsdictionary dictionary]). We'll move the configuration out of our object graph to our normal objects, separating them so that the code is cleaner, testable, and business logic clearer.
When adding a convenience constructor, you should first ensure that it is necessary. If an object has only a few parameters in the Init method, and these parameters do not have the correct default value, it is unnecessary to add a convenience constructor and the caller should use the standard Init method directly.
In order to configure our objects, we will collect our dependencies from 4 points:
1. There is no default value for the tangent. including Boolean or number values, they may vary depending on the values in the different instance variables. So these values should be passed as parameters to the convenience constructor;
2. There is a shared object. This also needs to be passed as a parameter to the convenience builder (e.g. a radio frequency). These objects may have been assigned as Singleton or through the parent class pointer;
3. The newly created object. If an object does not share this dependency with other objects, that other object (the same class) should create a new dependent object within the traversal constructor.
4. System single case. The singleton provided in the cocoa can be accessed directly, such as the File Manager Singleton [Nsfilemanager Defaultmanager], where it is clear that only one instance of your application will be used.
Here is a simple initial convenience builder for racing cars
+ (Instancetype) racecarwithpitradiofrequency: (rcradiofrequency *) frequency; { Rcengine *engine = [[Rcengine alloc] init]; Rctransmission *transmission = [[Rctransmission alloc] init]; Rcwheel * (^wheelfactory) () = ^{ return [[Rcwheel alloc] init]; }; return [[Self alloc] initwithengine:engine transmission:transmission pitradiofrequency:frequency Wheelfactory:wheelfactory];}
The traversal constructor should be placed in a more suitable place to separate from the class. It is usually placed in the same *.m file, but it should be placed in the @interface racecar (fooconfiguration) when specified through a configuration such as Foo object. Category, and is named similar to Fooracecar.
System single Case
There are many objects in cocoa that only one instance exists, such as,, [UIApplication sharedApplication] [NSFileManager defaultManager] [NSUserDefaults standardUserDefaults] , and [UIDevice currentDevice] so on. If an object relies on one of these objects, it should be included as a parameter. Even if there is only such an instance in your application. In your tests, you might want to simulate an instance or create an instance before testing to avoid testing dependencies.
Instead of creating a global singleton in your code, it is recommended to create a single instance in an object that, when it is first used, injects it into other objects that depend on it.
Non-modifiable constructors
Sometimes, the initialization method/constructor method of a class cannot be modified or cannot be called directly. In this case, you need to use setter injection. The following code:
An example where we cannot invoke the initialization method directly. Rcracetrack *racetrack = [Objectyoucantmodify createracetrack];//We can still use properties to configure our racing path racetrack.width = 10; Racetrack.numberofhairpinturns = 2;
Setter injection allows you to configure this object, but it introduces additional variability so that additional testing and processing must be done in the design of this class. Fortunately, there are two main scenarios where the initialization method cannot be accessed and modified, and both of these situations can be avoided.
Class Registration
The use of the "Class registration" Factory mode means that objects cannot modify their initialization methods. See Code:
Nsarray *racecarclasses = @[ [Rcfastracecar class], [Rcslowracecar class],]; Nsmutablearray *racecars = [[Nsmutablearray alloc] Init];for (Class racecarclass in racecarclasses) { //All cars must have the same initial Method (in this case, the "Init" method). This means that we cannot customize different subclasses [racecars Addobject:[[racecarclass alloc] init];}
a simple replacement method is to use the factory block instead
typedef rcracecar * (^rcracecarfactory) (); Nsarray *racecarfactories = @[ ^{return [[Rcfastracecar alloc] initwithtopspeed:200];}, ^{return [[ Rcslowracecar alloc] initwithleatherplushiness:11]; }]; Nsmutablearray *racecars = [[Nsmutablearray alloc] Init];for (rcracecarfactory racecarfactory in raceCarFactories) { ///Now, we don't have to care if the initialization method is called. [Racecars addobject:racecarfactory ()];}
storyboards
Storyboards provides a convenient way to build our interface, but it also poses problems in dependency injection. Especially when instantiating an initialized view controller in storyboard, it does not allow you to choose which initialization method to invoke. Similarly, when a segue is defined in storyboard, the target controller cannot let you specify the invocation of that initialization method when it is instantiated.
The workaround is to avoid using storyboard. This seems like an extreme solution, but we find that storyboard brings other problems in the development of large teams. In addition, storyboard does not lose all of its benefits, except for the segues provided by storyboard,Xib offers the same benefits as storyboard, and Xib allows you to customize the initialization method.
Public and private
Dependency injection encourages you to expose more objects on your shared interface. As mentioned above, this has many advantages. But when it comes to building a framework, it makes your common APIs bloated. Before using dependency injection, public object A has used private object B (object B in turn uses private object C), but object B and object C have never been exposed from the framework. With dependency injection, object A has object B in its public initialization method, and object B in turn causes object C to be exposed in its initialization method.
In public ObjectA.h. @interface objecta//Because the initialization method uses the reference to object B, we need to introduce its header file before using object B-(instancetype) INITWITHOBJECTB: (OBJECTB *) OBJECTB; @end @interface objectb//here too: we need to expose objectc.h-(instancetype) INITWITHOBJECTC: (OBJECTC *) ObjectC ; @end @interface objectc-(instancetype) init; @end
both object B and object C are concrete implementations, and you don't want the user of the framework to care about them. At this point we can solve by the Protocol (Protocol).
@interface objecta-(Instancetype) INITWITHOBJECTB: (id <ObjectB>) OBJECTB, @end//This protocol only exposes OBJECTA needs the original OBJECTB. We do not create a hard-dependent @protocol objectb-(void) methodneededbyobjecta in a specific OBJECTB (or OBJECTC) implementation @end
End
Dependency Injection is a natural presence in objective-c, as well as in Swift. Using it wisely can make your code more readable, more testable, and more maintainable.
Dependency Injection--make iOS code more concise