In the last part of the cycle, we met the Dependency Injection framework for iOS - Typhoon , and looked at basic examples of its use in the Rambler project. Mail. This time we delve into the study of its internal structure.Cycle "Manage dependencies in iOS applications correctly"
Introduction
To begin with, let's analyze a small glossary of terms that will be actively used in this article:
- Assembly (reads as [essambles] ). The closest Russian equivalent is assembly, construction. In Typhoon, these are objects that contain configurations of all dependencies of an application; in essence, they are the backbone of the entire architecture. For the outside world, being activated, they behave like ordinary factories.
- Definition . As for the translation into Russian - I am most impressed by the configuration, as the closest option to the original. TyphoonDefinition - these are objects that are a kind of dependency model that contain information such as the class of the object being created, its properties, and the type of life cycle. Most of the examples from the previous article dealt with various options for setting up TyphoonDefinition .
- Scope . Everything is simple here - this is the type of the life cycle of an object created with Typhoon.
- Activation . The process by which all objects that inherit from TyphoonAssemby start typing real class instances instead of TyphoonDefinition . The essence and principle of the activation will be discussed below.
And once again I emphasize that it is very important to understand the basic principles of the framework - after that we can safely move on and explore all the other Typhoon buns, without dwelling on the details of their implementation.
In order not to clutter up the article with huge code listings, I will periodically refer to certain framework files, but bring only the most interesting moments. I draw your attention to the fact that the current version of Typhoon Framework at the time of this writing -
3.1.7 .
')
Initialization
The life cycle of an application using Typhoon is as follows:
- Call main.m
- Creating UIApplication - [UIApplication init]
- Creating UIAppDelegate - [UIAppDelegate init]
- Calling the setDelegate Method: UIApplication Instance You Have Created
- Calling the implementation of setDelegate in the TyphoonStartup class :
- Calling the -applicationDidFinishLaunching: withOptions: UIAppDelegate Instance

It is in setDelegate
: the creation and activation of the start assemblies takes place.
Automatic loading of factories is possible in two cases: we either specified their classes in
Info.plist under the key
TyphoonInitialAssemblies :
+ (id) factoryFromPlistInBundle: (NSBundle *) bundle+ (id)factoryFromPlistInBundle:(NSBundle *)bundle { TyphoonComponentFactory *result = nil; NSArray *assemblyNames = [self plistAssemblyNames:bundle]; NSAssert(!assemblyNames || [assemblyNames isKindOfClass:[NSArray class]], @"Value for 'TyphoonInitialAssemblies' key must be array"); if ([assemblyNames count] > 0) { NSMutableArray *assemblies = [[NSMutableArray alloc] initWithCapacity:[assemblyNames count]]; for (NSString *assemblyName in assemblyNames) { Class cls = TyphoonClassFromString(assemblyName); if (!cls) { [NSException raise:NSInvalidArgumentException format:@"Can't resolve assembly for name %@", assemblyName]; } [assemblies addObject:[cls assembly]]; } result = [TyphoonBlockComponentFactory factoryWithAssemblies:assemblies]; } return result; }
or implemented the
-initialFactory method in our
AppDelegate :
+ (TyphoonComponentFactory *) factoryFromAppDelegate: (id) appDelegate + (TyphoonComponentFactory *)factoryFromAppDelegate:(id)appDelegate { TyphoonComponentFactory *result = nil; if ([appDelegate respondsToSelector:@selector(initialFactory)]) { result = [appDelegate initialFactory]; } return result; }
If neither one nor the other has been done, the assembly will have to be created by hands elsewhere in the code, which is not recommended.
You can find out more about the initialization details of Typhoon in the following source files:
- TyphoonStartup.m
- TyphoonComponentFactory.m
Activation
This process is key to the framework. By activation is meant the creation of an object of the class
TyphoonBlockComponentFactory , the instance of which is “under the hood” of all activated assembly. Thus, any assembly plays the role of an interface for communicating with a real factory.
Let's see what happens, without really going into details:
- In TyphoonBlockComponentFactory , the initializer -initWithAssemblies: is called, and the assembly array is passed to the input, which must be activated.
- Each of the definitions created by the activated assembly is assigned its own unique key (random string + method name).
- All TyphoonDefinition is added to the registry array of the newly created TyphoonBlockComponentFactory .

Of course, the matter is not limited to these three points:
aspects are added for each registered
TyphoonDefinition , getters of all dependencies are sorted, creating an initialization chain of the object graph, and pools of instances are created in
TyphoonBlockComponentFactory - in general, a large number of different actions are performed to ensure the framework works. In this article, we will not go into the details of each of the procedures in question, as this may distract from understanding the general working principles of Typhoon.
We looked at how
TyphoonAssembly is activated - it remains to understand why this should be done at all. Every time we manually pull some method from the assembly that gives
TyphoonDefinition to the dependency, the following happens:
- (void) forwardInvocation: (NSInvocation *) anInvocation - (void)forwardInvocation:(NSInvocation *)anInvocation { if (_factory) { [_factory forwardInvocation:anInvocation]; } ... }
In
_factory, the resulting
NSInvocation is processed and converted to a call to the following method:
- (id) componentForKey: (NSString *) key args: (TyphoonRuntimeArguments *) args - (id)componentForKey:(NSString *)key args:(TyphoonRuntimeArguments *)args { if (!key) { return nil; } [self loadIfNeeded]; TyphoonDefinition *definition = [self definitionForKey:key]; if (!definition) { [NSException raise:NSInvalidArgumentException format:@"No component matching id '%@'.", key]; } return [self newOrScopeCachedInstanceForDefinition:definition args:args]; }
The key generated from the selector method gets one of the
TyphoonBlockComponentFactory definitions registered in the
TyphoonBlockComponentFactory definition, and then a new instance is created on its basis, or the cached is reused.
You can learn more about the activation process in the following source files:
- TyphoonAssembly.m
- TyphoonBlockComponentFactory.m
- TyphoonTypeDescriptor.m
- TyphoonAssemblyDefinitionBuilder.m
- TyphoonStackElement.m
Work with Storyboard
Under the hood, Typhoon uses its UIStoryboard
subclass -
TyphoonStoryboard . The first feature that catches the eye is the factory method, which differs from its parent by an additional parameter — factory:
+ (TyphoonStoryboard *)storyboardWithName:(NSString *)name factory:(id<TyphoonComponentFactory>)factory bundle:(NSBundle *)bundleOrNil;
It is in this factory that implements the
TyphoonComponentFactory protocol that the definitions for the current storyboard screens will be searched. Let's look at all the steps of dependency injection in ViewControllers:
- First of all we get to the -instantiateViewControllerWithIdentifier: method, redefined in TyphoonStoryboard .
- An instance of the desired controller is created by calling super'a.
- Injection of all dependencies of the current controller and its child controllers is initiated:
- (void) injectPropertiesForViewController: (UIViewController *) viewController - (void)injectPropertiesForViewController:(UIViewController *)viewController { if (viewController.typhoonKey.length > 0) { [self.factory inject:viewController withSelector:NSSelectorFromString(viewController.typhoonKey)]; } else { [self.factory inject:viewController]; } for (UIViewController *controller in viewController.childViewControllers) { [self injectPropertiesForViewController:controller]; } }
- In TyphoonBlockComponentFactory , a procedure that is already familiar to us takes place — the type TyphoonDefinition corresponding to the current class is searched for and the dependency graph is injected into it.
Now I will not dwell on the specific implementation of working with
TyphoonStoryboard in the application - this topic will be covered in one of the following articles.
More information about the implementation of the storyboard can be found in the following source files:
- TyphoonStoryboard.m
- TyphoonBlockComponentFactory.m
TyphoonDefinition
In almost every snippet I have mentioned, in one form or another, the class
TyphoonDefinition is found. As I mentioned when enumerating terms,
TyphoonDefinition is a kind of configuration class for the dependency being created - therefore, its interface is of particular interest to us:
- Class _type - class of the created dependency.
- NSString * _key is a unique key generated when Typhoon is activated,
- TyphoonMethod * _initializer - an object created by initializer injection , containing the signature of the desired initializer and a collection of its parameters,
- TyphoonMethod * _beforeInjections - the method that will be called before the injection of dependencies,
- TyphoonMethod * _afterInjections - the method that will be called after the injection of dependencies,
- NSMutableSet * _injectedProperties - a collection of dependencies installed via property injection ,
- NSMutableOrderedSet * _injectedMethods - a collection of methods to which certain dependencies ( method injection ) are passed,
- TyphoonScope scope - type of life cycle of the object being created,
- TyphoonDefinition * _parent - the base TyphoonDefinition , all properties of which will be inherited by the current,
- BOOL abstract is a flag indicating that the current configuration can only be used to implement inheritance, and the object it represents should never be created directly.
Of all the above properties, the
scope of the object deserves special attention.
More information about how
TyphoonDefinition works can be found in the following source files:
- TyphoonDefinition.m
- TyphoonAssemblyDefinitionBuilder.m
- TyphoonFactoryDefinition.m
- TyphoonInjectionByReference.m
- TyphoonMethod.m
TyphoonScope
It is important to clearly understand that, speaking of the different types of the object's life cycle, we are still rigidly tied to the lifetime of the
TyphoonBlockComponentFactory instance
used - if this factory is freed from memory, all object graphs will be released along with it.
Let's see what each of the
TyphoonScope values
leads to :
- TyphoonScopeObjectGraph
In the process of building a dependency graph, an object with such scope will be created only once. Technically, it looks like this: before creating an instance (of any definition), a pool is created for object graph scoped objects, in the process of building a dependency graph, all object-graph-scoped go to this pool, and after the instance is created, this pool cleared. - TyphoonScopePrototype
Each time a TyphoonDefinition is accessed with such a scope, a new instance will be created. - TyphoonScopeSingleton
An object with such a life cycle will live throughout the TyphoonComponentFactory . - TyphoonScopeLazySingleton
As the name implies, this is a singleton that will be created at the time of the first access to it. - TyphoonScopeWeakSingleton
A singleton created by using such a TyphoonDefinition is in memory exactly as long as at least one object refers to it - otherwise it will be released.
The created object, depending on the scope property of its configuration, is stored in one of the
TyphoonComponentFactory pools, each of which works in a certain way.
More about the principles of the Typhoon dependency cache can be found in the following sources:
- TyphoonComponentFactory.m
- TyphoonWeakComponentPool.m
- TyphoonCallStack.m
Conclusion
We managed to consider only the most basic working principles of
Typhoon Framework - initialization, factory activation, the device
TyphoonAssembly ,
TyphoonStoryboard ,
TyphoonDefinition and
TyphoonBlockComponentFactory , the life cycle features of the objects created. The library contains a lot of interesting concepts, the implementation of which sometimes just fascinates.
I strongly recommend spending a few days and digging in their study with your head - this is more than a worthy alternative to studying the many lessons in the style
“We work in Xcode with the Swift mouse for free and without SMS” and
“Advanced animation of the file upload indicator” .
In the next part of the cycle, you will learn how to avoid the emergence of one huge factory, properly break the Assembly-level architecture into modules and cover the whole thing with tests.
Cycle "Manage dependencies in iOS applications correctly"
useful links