Implementation of iOS control inversion (IoC) and Dependency injection (DI)

Source: Internet
Author: User

Background

Springmvc, who has recently been exposed to a period of time, is impressed with its inversion of control (IoC) and Dependency Injection (DI), and has since been thinking about how to achieve these two features with OC language. The Java language comes with an annotation feature that brings great convenience to IOC and DI, and it requires a little bit of skill to implement these two functions better on OC.

Control inversion and Dependency injection control inversion

In a nutshell, the creation of a class object is replaced by the manual new mode from within the IOC container, which is a kind of control inversion, for example, we are now going to create a ClassA class, the general method is

ClassA *a = [ClassA new];

If control inversion is used, the ClassA object is obtained from within the container, and the object is created by the container.

ApplicationContext *context = [ApplicationContext sharedContext];ClassA *a = [context getInstanceByClassName:@"ClassA"];
Dependency Injection

The so-called dependency injection, refers to a class object should not be responsible for finding its dependency properties, but to the container to handle. For example, the ClassA class has a ClassB class object, as shown below.

@interface ClassA : NSObject@property(nonatomicstrong) ClassB *b;@end

In general, you want to pass CLASSB objects to ClassA after they are created, as shown below.

newnew];a.b = b;

If handled by the container, the container automatically creates ClassA, ClassB, and automatically injects ClassB instances into ClassA objects based on the ClassA dependency property type ClassB.

Advantages

By using control inversion and dependency injection, the creation of objects and the injection of dependent objects are done by the IOC container, which not only reduces the coupling of the code, but also reduces the amount of code.

Class-and-property adornments

These two functions are implemented at runtime through reflection, and the specifics of the container technical details are discussed below, and the main discussion here is how to decorate the objects that are created by the container and the properties of the dependency injection.

Annotations in Java

In Java, classes that are to be created by the spring container are decorated with annotations, such as the controller and service in Javaweb are decorated by @controller and @service, respectively, as shown below.

// 控制器@Controllerpublicclass SomeController {...}// 服务@Servicepublicclass SomeService {...}

The controller relies on the service to process the business logic through the services, and the @autowired modifies this property to complete the automatic injection, as shown below.

@Controllerpublic class AdministratorController {    @Autowired    private SomeService serv;...}

With @autowired annotations, the IOC container finds an instance of the dependent object based on the attribute type (someservice) and injects it into the controller's Serv property. In this process, the Controller, service, and injection do not need to write extra code, only need to pass the annotation adornment.

Implementation in OC

Since OC does not have annotation attributes, there are other ways to tag classes and attributes, and I've thought about a better way to tag classes through protocols, and to tag attributes with additional attributes.
* In order to mimic the annotations of @controller, @Service and other cosmetic classes, we use the Ioccomponents protocol to mark classes to indicate that these classes are handled by the container.

@interface SGService : NSObject <IOCComponents>...@end
    • In order to mimic the @autowired implementation of injection by type, we add a property before the property to be injected, the type of the property is Typeannotation, which we call the annotation attribute , which is not present by @class Declaration, but for the purpose of marking, All properties that are obtained in reflection are ordered in order, so the attributes after each annotation attribute are required for dependency injection, as shown below.
@interface SGService : NSObject <IOCComponents>@property (nonatomicweakreadonly) TypeAnnotation *autowired_0;@property (nonatomicstrong) SGMapper *mapper;@end

In this way, Autowired_0 and Mapper are adjacent at the time of reflection, so it is possible to determine mapper needs to be injected, only to reflect the property information, and to find and inject it from the container, in order to facilitate the definition of such annotation properties, We use a macro as shown below.

#defineAutowired(num@property (nonatomic, weak, readonly) TypeAnnotation *autowired_##num;

Then the code above can be simplified as follows.

@interface SGService : NSObject <IOCComponents>Autowired(0)@property (nonatomicstrong) SGMapper *mapper;@end

The number here is for multiple annotations to cause a duplicate definition of the attribute, which can be numbered starting at 0.

An example of an implementation of OC

here it is assumed that the IOC container has been completed, the main presentation process, and the technical details of the container are discussed below.
There is a service class and a mapper class, and the service relies on mapper, as the code below.

Definition and modification of a class
    • Ioccomponents is used to identify that the class is created by the container
    • Autowired (x) indicates that the property is dependent injected by type
@interface SGService : NSObject <IOCComponents>Autowired(0)@property (nonatomicstrong) SGMapper *mapper;@end
@interface SGMapper : NSObject <IOCComponents>@end
Scan configuration

Similar to the spring Applicationcontext.xml, here the applicationcontext.plist to configure the characteristics of the class to be scanned, currently provides a prefix and class list, for classes with a common prefix can be configured with a prefix to implement the scan, for classes without a prefix, individually configured To the class list, as shown in.

Validation and use

With the above configuration, the IOC container can complete service and mapper creation and inject mapper into the service to verify the following.

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {    SGApplicationContext *context = [SGApplicationContext sharedContext];    SGService *serv = [context getInstanceByClassName:@"SGService"];    printf("%s -> %s\n",serv.description.UTF8String, serv.mapper.description.UTF8String);    returnYES;}

Print as follows

<SGService: 0x7ff34bc749f0><SGMapper: 0x7ff34bc74920>
Implementation of the IOC container

The core functions of the IOC container are object creation, dependency lookup, and dependency injection, all of which need to be implemented using runtime reflection, followed by the process of container initialization to describe the OC runtime functions used, which can be objc/runtime.h used after inclusion.

1.IoC container

The container uses Sgapplicationcontext to manage the class objects that need to be created by the container, instancemap whether the storage object has been created by a dictionary type of property, and the object in the container is now a singleton, the logic is simple, Each time an object is fetched by type, it is checked whether the object has already been created, if it was created, or returned after it is created, this will be objc_getClass checked by the run-time function to see if the class has been loaded, as instancemap.

- (ID) Getinstancebyclassname: (NSString*) ClassName {if( Self. Instancemap[ClassName] = =Nil{Class clazz = nsclassfromstring (className);//Check whether the class has been loaded and prevent an error when an undefined class is instantiated        if(!objc_getclass (ClassName. Utf8string)) {return Nil; }IDinstance = [Clazz new]; Self. Instancemap[ClassName] = instance; }return  Self. Instancemap[ClassName];}
2. Scan Class List

objc_getClassListyou can get a list of classes through a function that has the following specific information.

int*bufferint bufferCount);

Buffer is an array of all classes, buffercount the size of the array, the return value is also an array size, because the first call is not known buffercount, so it can be called twice, the first to get Buffercount, the second time to initialize the class list of arrays, the specific code is as follows , please see the notes for details.

- (void) Scanclasses {intClassCount = Objc_getclasslist (NULL,0); Class *classlist = (class *) malloc (ClassCount *sizeof(Class)); ClassCount = Objc_getclasslist (classlist, ClassCount);//For holding OC arrays for classes that require IOC container processing    Nsmutablearray*temp = @[]. Mutablecopy;//Get ioccomponents protocol for judging the MarkProtocol *protocol = Objc_getprotocol ("Ioccomponents"); for(inti =0; i < ClassCount; i++) {Class clazz = classlist[i];NSString*classname = Nsstringfromclass (clazz);//The first judging condition corresponds to the Scan-class feature configuration in Applicationcontext.plist, and only qualified classes can be added        ///second condition Check ioccomponents tag, tagged class is processed by IOC container        if([ SelfIsvalidiocclassnamed:classname] && [clazz Conformstoprotocol:protocol]) {[Temp addobject:classname]; }    }//Store the classes that the IOC needs to process     Self. Diclasses= temp;//Because the class list is created by malloc, you need to manually releaseFree (classlist);//Handle Dependency injection based on annotation properties[ SelfScanannotation];}
3. Scan class properties and attribute injection

To scan all private and public properties of a class, use the following function.

*class_copyPropertyListint*outCount);

The method can list all properties, first private after public, in the order of definition from top to bottom list, its return value is a objc_property_t structure array, from the objc_property_t structure can get the various information of the property, for the name can be passed property_getName function is obtained directly, and other information needs to be property_getAttributes obtained by means of a method to obtain a string describing the property, and then split. With this step, you can get the location of all the annotation attributes, and thus get all the attributes you need to inject, based on the type of these attributes.
To complete the injection, you need to first get the Ivar that represents the property, then inject the instance, and the functions to get the Ivar and injected properties are as follows.

// 注意得到Ivar时的属性名为实例变量名而不是property名,例如@property定义的xx,则这里应该写作_xxconstchar *name);// 将value注入到obj实例的 ivar实例变量上void object_setIvar(idid value);

The specific code is as follows, please see the comments in detail.

- (void) Scanannotation {//Scanclasses the class that needs to be processed by the IOC container to be traversed     for(Nsuinteger i =0; I < Self. Diclasses. Count; i++) {NSString*classname = Self. Diclasses[i]; Class class = Nsclassfromstring (ClassName);unsigned intOutcount;//Reflect all propertiesobjc_property_t *props = Class_copypropertylist (class, &outcount);//Save all annotation attributes, the annotation attribute contains the position index (index), name, and type (the types), stored by a model class Sgannotation        Nsmutablearray*annotations = @[]. Mutablecopy;//Save all property information, each attribute contains the name (name) and type, which is stored by a model class Sgproperty        Nsmutablearray*properties = @[]. Mutablecopy;//Traverse All Properties         for(Nsuinteger i =0; i < Outcount; i++) {objc_property_t prop = Props[i];NSString*propname = [[NSStringAlloc] Initwithcstring:property_getname (prop) encoding:nsutf8stringencoding];//This section of code is used to get the type from the string that describes the property, using regular and string processing            NSString*propattrs = [[NSStringAlloc] Initwithcstring:property_getattributes (prop) encoding:nsutf8stringencoding];NsrangeRange = [Propattrs rangeofstring:@"@\".*\""Options:nsregularexpressionsearch];if(Range. location!=Nsnotfound) {Range. location+=2; Range. Length-=3;NSString*typename = [Propattrs substringwithrange:range];//If the current property is an annotation property, the annotaions is logged in                if([TypeName isequaltostring:@"Typeannotation"]) {sgannotation *anno = [sgannotation new]; Anno. Index= i; Anno. Name= propname; Anno. Type= TypeName;                [Annotations Addobject:anno]; }//Record each propertySgproperty *SP = [Sgproperty new]; Sp. Name= propname; Sp. Type= TypeName;            [Properties ADDOBJECT:SP]; }        }//Scan class properties End        //Get an instance of the class from the container        IDDiinstance = [ SelfGetinstancebyclassname:classname];//Iterate through annotations to get all the modified properties         for(Nsuinteger i =0; I < Annotions. Count;            i++) {sgannotation *annotation = annotations[i]; Sgproperty *prop = properties[annotation. Index+1];NSString*typename = Prop. Type;NSString*varname = Prop. Name;//Get dependent object and inject the property into the callout adornment            IDDestinstance = [ SelfGetinstancebyclassname:typename]; Ivar Ivar = class_getinstancevariable ([Diinstance class], [NSStringstringwithformat:@"_%@", VarName]. Utf8string);        Object_setivar (Diinstance, Ivar, Destinstance); }    }//Scan classes End}
Summarize

Through this processing, the OC language to the IOC and DI is the basic implementation, this is only a search, there are many areas need to optimize and perfect.

Implementation of iOS control inversion (IoC) and Dependency injection (DI)

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.