Objection源码分析

一、简介

Objection是一个iOS中轻量级的对DIIoC的实现,不知道DIIoC的请移步至iOS组件通信方案Objection只有Object-C版本,没有Swift版本,如果你想在Swift中使用这种容器框架,可以了解下Typhoon,二者对于DI的实现方式不同,原理也不同,本篇文章主要分析Objection

它的优点有哪些呢?

  • 支持我们进行面向接口编程,有利于项目解耦(组件化)。
  • 对象的销毁由系统管理,而Objection可以帮助我们完成对象的创建工作。
  • 其他优点自行体会。

二、关键词及其实现

提前了解关键词有助于我们更好的理解该框架的底层实现,Objection中的关键词有如下几个:

  • Annocation(注解)
  • KVC
  • runtime

1.先来说注解

Objection中的注解是通过宏定义来实现的,所以掌握注解的使用至关重要,这些宏定义全部放在了Objection.h文件中,包括如下几个:
(1)objection_register(value)
向容器中注册类(Class),未来使用的时候,容器会直接拿到这个类然后进行实例化。

(2)objection_register_singleton(value)
功能同(1),不过这里是注册单例。

(3)objection_requires(args...)
基于属性的注入方式依赖此宏,这个宏用于定义某个类进行实例化时所依赖的属性,如果这个属性所属的类是我们之前已经注入到容器中的,那么容器就会取出这个类的实例对象,并将实例对象赋值给这个属性。

(4)objection_requires_sel(args...)
这个也是基于属性的注入方式,和(3)是一样的,只不过在你获取或注入某个属性值的时候,会走getter方法。

(5)objection_requires_names(namedDependencies)
这个是对(3)的功能补充,用于给注入的属性起个别名。

(6)objection_initializer_sel(selectorSymbol, args...)
基于构造器的注入方式依赖此宏,这个宏用于定义某个类的初始化函数,可以看到展开后有两个参数,第一个参数是你自定义的初始化函数,第二个参数是个可变类型的参数,也就是你初始化函数所依赖的参数需要在这里指明,举个例子:

Person.m

- (instance)initWithName:(NSString *)name age:(NSString *)age {
   if(self = [super init]){
    self.name = name;
    self.age = age;
  }
  return self;
}

objection_initializer_sel(initWithName:age:)

(7)objection_initializer(selectorSymbol, args...)
这个宏内部会调用(6)

2.KVC

Objection中对于注入功能的实现是通过KVC来完成的,这也是容器完成创建实例对象的最后一步,如下所示。

- (id)buildObject:(NSArray *)arguments initializer: (SEL) initializer {
    
    id objectUnderConstruction = nil;
    
    if(initializer != nil) {
        objectUnderConstruction = JSObjectionUtils.buildObjectWithInitializer(self.classEntry, initializer, arguments);
    } else if ([self.classEntry respondsToSelector:@selector(objectionInitializer)]) {
        objectUnderConstruction = JSObjectionUtils.buildObjectWithInitializer(self.classEntry, [self initializerForObject], [self argumentsForObject:arguments]);
    } else {
        objectUnderConstruction = [[self.classEntry alloc] init];
    }

    if (self.lifeCycle == JSObjectionScopeSingleton) {
        _storageCache = objectUnderConstruction;
    }
    
     //最后一步:注入依赖的属性值!
    JSObjectionUtils.injectDependenciesIntoProperties(self.injector, self.classEntry, objectUnderConstruction);
    
    return objectUnderConstruction;
}
static void InjectDependenciesIntoProperties(JSObjectionInjector *injector, Class klass, id object) {
    if ([klass respondsToSelector:@selector(objectionRequires)]) {
        NSSet *properties = [klass objectionRequires];
        NSMutableDictionary *propertiesDictionary = [NSMutableDictionary dictionaryWithCapacity:properties.count];
        for (NSString *propertyName in properties) {
            JSObjectionPropertyInfo propertyInfo;
            id desiredClassOrProtocol;
            _getPropertyInfo(klass, propertyName, &propertyInfo, &desiredClassOrProtocol);
            id theObject = [injector getObject:desiredClassOrProtocol];
            _validateObjectReturnedFromInjector(&theObject, propertyInfo, desiredClassOrProtocol, propertyName);
            [propertiesDictionary setObject:theObject forKey:propertyName];
        }

        //使用KVC注入属性值
        [object setValuesForKeysWithDictionary:propertiesDictionary];
    }

    if ([klass respondsToSelector:@selector(objectionRequiresNames)]) {
        NSDictionary *namedProperties = [klass objectionRequiresNames];
        NSMutableDictionary *propertiesDictionary = [NSMutableDictionary dictionaryWithCapacity:namedProperties.count];
        for (NSString *namedPropertyKey in [namedProperties allKeys]) {
            NSString* propertyName = [namedProperties valueForKey:namedPropertyKey];
            JSObjectionPropertyInfo propertyInfo;
            id desiredClassOrProtocol;
            _getPropertyInfo(klass, propertyName, &propertyInfo, &desiredClassOrProtocol);
            id theObject = [injector getObject:desiredClassOrProtocol named:namedPropertyKey];
            _validateObjectReturnedFromInjector(&theObject, propertyInfo, desiredClassOrProtocol, propertyName);
            [propertiesDictionary setObject:theObject forKey:propertyName];
        }

        //使用KVC注入属性值
        [object setValuesForKeysWithDictionary:propertiesDictionary];
    }

    if ([object respondsToSelector:@selector(awakeFromObjection)]) {
        //如果对象重写了awakeFromObjection方法,这里还会调用awakeFromObjection方法,方便你做一些其他的操作
        [object awakeFromObjection];
    }
}

(3)runtime
方法的调用过程中使用了大量runtime技术,比如:

  • 遍历类的属性
  • 反射
  • 消息传递

三、整体架构

了解其架构设计可以更好的帮助我们理解其实现原理。

我画了一个草图用来表示Objection主要的工作流程,如下:

流程图

1.总体流程

入口类是JSObjection,有几个常用的方法是:

+ (JSObjectionInjector *)createInjectorWithModules:(JSObjectionModule *)first, ... NS_REQUIRES_NIL_TERMINATION;
+ (JSObjectionInjector *)createInjectorWithModulesArray:(NSArray *)modules;
+ (JSObjectionInjector *)createInjector:(JSObjectionModule *)module;
+ (JSObjectionInjector *)createInjector;

通过这几个方法可以看出,JSObjection会根据若干个Module实例化出Injector对象,对应最左侧那一条流程,也就是JSObjection会收集所有Module对象,然后根据这些Module对象实例化出Injector对象。

对于开发者来说,操作JSObjectionModule对象可能是最常见的操作之一,顾名思义,JSObjectionModule代表一个模块,怎么划分模块任你设计,你可以将不同的页面划分成不同的模块,比如一个商品详情页面可以对应一个Module,一个会话详情页面可以对应一个Module;也可以将一条业务线划分成一个Module,比如商品Module可以包括列表、详情、订单等页面,一个即时通讯Module可以包括会话列表、会话详情、个人详情等页面。

JSObjectionModule包含哪些操作呢?很多很多,多到你用不过来。

- (void)bind:(id)instance toClass:(Class)aClass;
- (void)bind:(id)instance toClass:(Class)aClass named:(NSString *)name;
- (void)bind:(id)instance toProtocol:(Protocol *)aProtocol;
- (void)bind:(id)instance toProtocol:(Protocol *)aProtocol named:(NSString *)name;
- (void)bindMetaClass:(Class)metaClass toProtocol:(Protocol *)aProtocol;
- (void)bindProvider:(id<JSObjectionProvider>)provider toClass:(Class)aClass;
- (void)bindProvider:(id<JSObjectionProvider>)provider toClass:(Class)aClass named:(NSString *)name;
- (void)bindProvider:(id<JSObjectionProvider>)provider toProtocol:(Protocol *)aProtocol;
- (void)bindProvider:(id<JSObjectionProvider>)provider toProtocol:(Protocol *)aProtocol named:(NSString *)name;
- (void)bindProvider:(id<JSObjectionProvider>)provider toClass:(Class)aClass inScope:(JSObjectionScope)scope;
- (void)bindProvider:(id<JSObjectionProvider>)provider toClass:(Class)aClass inScope:(JSObjectionScope)scope named:(NSString *)name;
- (void)bindProvider:(id<JSObjectionProvider>)provider toProtocol:(Protocol *)aProtocol inScope:(JSObjectionScope)scope;
- (void)bindProvider:(id<JSObjectionProvider>)provider toProtocol:(Protocol *)aProtocol inScope:(JSObjectionScope)scope named:(NSString *)name;
- (void)bindClass:(Class)aClass toProtocol:(Protocol *)aProtocol;
- (void)bindClass:(Class)aClass toProtocol:(Protocol *)aProtocol named:(NSString*)name;
- (void)bindClass:(Class)aClass toProtocol:(Protocol *)aProtocol inScope:(JSObjectionScope)scope named:(NSString*)name;
- (void)bindClass:(Class)aClass toClass:(Class)toClass;
- (void)bindClass:(Class)aClass toClass:(Class)toClass named:(NSString*)name;
- (void)bindClass:(Class)aClass toClass:(Class)toClass inScope:(JSObjectionScope)scope named:(NSString*)name;
- (void)bindBlock:(id (^)(JSObjectionInjector *context))block toClass:(Class)aClass;
- (void)bindBlock:(id (^)(JSObjectionInjector *context))block toClass:(Class)aClass named:(NSString *)name;
- (void)bindBlock:(id (^)(JSObjectionInjector *context))block toProtocol:(Protocol *)aProtocol;
- (void)bindBlock:(id (^)(JSObjectionInjector *context))block toProtocol:(Protocol *)aProtocol named:(NSString *)name;
- (void)bindBlock:(id (^)(JSObjectionInjector *context))block toClass:(Class)aClass inScope:(JSObjectionScope)scope;
- (void)bindBlock:(id (^)(JSObjectionInjector *context))block toClass:(Class)aClass inScope:(JSObjectionScope)scope named:(NSString *)name;
- (void)bindBlock:(id (^)(JSObjectionInjector *context))block toProtocol:(Protocol *)aProtocol inScope:(JSObjectionScope)scope;
- (void)bindBlock:(id (^)(JSObjectionInjector *context))block toProtocol:(Protocol *)aProtocol inScope:(JSObjectionScope)scope named:(NSString *)name;
- (void)bindClass:(Class)aClass inScope:(JSObjectionScope)scope;
- (void)registerEagerSingleton:(Class)aClass;
- (BOOL)hasBindingForClass:(Class)aClass;
- (BOOL)hasBindingForClass:(Class)aClass withName:(NSString*)name;
- (BOOL)hasBindingForProtocol:(Protocol *)protocol;
- (BOOL)hasBindingForProtocol:(Protocol *)protocol withName:(NSString*)name;
- (void)configure;

可以看出Module支持各种绑定操作:

  • 我们可以将ClassProtocol绑定在一起,通过protocol拿到Class的实例对象,此种方式为我们进行面向接口编程提供了很好的支持。
    举个例子,假设现在有个Car类,我们使用Car类实例化出一个对象,使用JSObjection的代码应该是这个样子的:
//CarDao
@protocol CarDao <NSObject>
- (void)fire;  //启动
@end

//Car
@interface Car : NSObject<CarDao>

@end

//绑定
JSObjectionModule *module = [[JSObjectionModule alloc] init];
[module bindClass:[Car class] toProtocol:@protocol(CarDao)];

//获取
id<CarDao> carDao = [[JSObjection defaultInjector] getObject:@protocol(CarDao)];
[carDao fire];
  • 可以将instanceClass/Protocol绑定在一起
  • 可以将实现了JSObjectionProvider接口的对象和Class/Protocol绑定在一起,在该对象中实现接口方法- (id)provide:(JSObjectionInjector *)context arguments:(NSArray *)arguments,在这个方法中实例化你需要的对象。
  • 还可以将blockClass/Protocol绑定在一起,block的返回值就是你需要的实例化对象。
  • 最后,我们也可以从JSObjectionModule派生出子类,重写configure方法,在这个方法中进行绑定操作。

绑定的下一步操作做什么?

JSObjectionModule中会把你需要绑定的数据封装成JSObjectionEntry对象(这个类还有3个子类,分别是JSObjectionProviderEntryJSObjectionInjectorEntryJSObjectionBindingEntry),然后把这个对象作为valueClass/Protocol作为key存放进一个bindings字典容器中。针对绑定的数据类型不通,封装所使用的子类类型也不同,总结就是:
(1)如果绑定的是instance,就会使用JSObjectionBindingEntry进行封装;
(2)如果绑定的是实现了JSObjectionProvider接口的对象,亦或是Class-Protocol,都会使用JSObjectionProviderEntry进行封装;
(3)其他都使用JSObjectionInjectorEntry进行封装。

JSObjectionInjector中也有一个字典容器变量contextJSObjectionInjector会将Module管理的bindings数据添加到context中,这样,我们后续便可以通过JSObjectionInjector对象获取到最初通过Module绑定的数据。

2.实例对象构建流程

说到对象的构建流程,不得不再把JSObjectionEntry拿出来,在这个类文件中,还有一个同名的JSObjectionEntry协议(接口),如下:

@protocol JSObjectionEntry<NSObject>
  
@property (nonatomic, readonly) JSObjectionScope lifeCycle;  //声明周期
@property (nonatomic, assign) JSObjectionInjector *injector;  //注入者

@required
- (id)extractObject:(NSArray *)arguments;  //根据args参数实例化出对象
+ (id)entryWithEntry:(JSObjectionEntry *)entry;
@optional
-(id)extractObject:(NSArray *)arguments initializer: (SEL)initializer;   //根据args参数实例化出对象

@end

知道这个协议中每个属性及方法的作用对于构建实例对象至关重要。

父类遵守了这套协议,并对这几个方法创建了默认的实现,而派生自JSObjectionEntry的3个子类均对这套协议的方法有着不同的实现,为什么会有不同的实现?因为每个子类绑定的数据类型不同,因此数据类型不同,实例化的方式也不同。

一个一个来看。

(1)JSObjectionBindingEntry

- (id)extractObject:(NSArray *)arguments {
    return _instance;
}

JSObjectionBindingEntry对接口方法的时间很简单,直接把绑定的instance返回就好了。

(2)JSObjectionProviderEntry

- (id)extractObject:(NSArray *)arguments {
    if (self.lifeCycle == JSObjectionScopeNormal || !_storageCache) {
        return [self buildObject:arguments];
    }

    return _storageCache;
}

- (id)buildObject:(NSArray *)arguments {
    id objectUnderConstruction = nil;
    if (_block) {
        objectUnderConstruction = _block(self.injector);
    }
    else {
        objectUnderConstruction = [_provider provide:self.injector arguments:arguments];
    }
    if (self.lifeCycle == JSObjectionScopeSingleton) {
        _storageCache = objectUnderConstruction;
    }
    return objectUnderConstruction;
}

如果你是通过JSObjectionModulebindBlock方法进行绑定操作,那么就会执行objectUnderConstruction = _block(self.injector);,直接把blockreturn的数据返回给你。

如果你是通过JSObjectionModulebindProvider方法进行绑定操作,那么就会执行objectUnderConstruction = [_provider provide:self.injector arguments:arguments];,找到你曾经绑定过的并实现了JSObjectionProvider接口的自定义对象,再调用privide:arguments:方法,这个方法实现中会返回你所实例化的对象。代码示例如下:

@implementation CarProvider
- (id)provide:(JSObjectionInjector *)context arguments:(NSArray *)arguments {
    Car *car = [context getObject:[Car class]];
    return car;
}
@end

(3)JSObjectionInjectorEntry

- (instancetype)extractObject:(NSArray *)arguments {
    return [self extractObject:arguments initializer:nil];
}

- (instancetype) extractObject:(NSArray *)arguments initializer:(SEL)initializer {
    if (self.lifeCycle == JSObjectionScopeNormal || !_storageCache) {
        return [self buildObject:arguments initializer: initializer];
    }
    return _storageCache;
}

- (id)buildObject:(NSArray *)arguments initializer: (SEL) initializer {
    
    id objectUnderConstruction = nil;
    
    if(initializer != nil) {
        objectUnderConstruction = JSObjectionUtils.buildObjectWithInitializer(self.classEntry, initializer, arguments);
    } else if ([self.classEntry respondsToSelector:@selector(objectionInitializer)]) {
        objectUnderConstruction = JSObjectionUtils.buildObjectWithInitializer(self.classEntry, [self initializerForObject], [self argumentsForObject:arguments]);
    } else {
        objectUnderConstruction = [[self.classEntry alloc] init];
    }

    if (self.lifeCycle == JSObjectionScopeSingleton) {
        _storageCache = objectUnderConstruction;
    }
    
    JSObjectionUtils.injectDependenciesIntoProperties(self.injector, self.classEntry, objectUnderConstruction);
    
    return objectUnderConstruction;
}

这个类实现的接口方法是最复杂的,也充分体现了DIIoC的思想。

从最后一个方法调用中,我们可以看出思路大致如下:
(1)这个方法有两个入参,分别是完整实例化依赖的参数和构造方法。
(2)先判断绑定的类有没有通过注解自定义构造方法,如果有,就会执行JSObjectionUtils.buildObjectWithInitializer(self.classEntry, initializer, arguments);Class有了,类的构造方法有了,构造方法依赖的参数也有了,有了这些就可以创建出一个完整的实例对象。
(3)如果没有通过注解自定义构造方法,会判断你有没有通过使用objection_initializer_sel注解,这一步的处理逻辑和(2)是相同的。
(4)如果(2)和(3)都没有,就会使用alloc init进行初始化操作。
(5)最后会使用JSObjectionUtils.injectDependenciesIntoProperties(self.injector, self.classEntry, objectUnderConstruction);将对象初始化依赖的属性值注入进去。
(6)如果某个类的属性是另外一个类,就会递归执行这个流程,确保所有属性都是经过初始化的。

更多的实现细节可以查看JSObjectionUtils这个结构体。

3.从容器获取实例

最开始我们提到,JSObjection会收集所有的JSObjectionModule创建出JSObjectionInjector对象,相当于JSObjectionInjector对象保存了我们绑定的数据(并不是所有数据,所以一定要注意使用正确的injector对象获取绑定的数据),所以想要获取IoC容器为什么创建的实例对象,我们也要通过JSObjectionInjector对象来获取,确切说应该是injector对象的这些方法:

- (id)getObject:(id)classOrProtocol;
- (id)getObject:(id)classOrProtocol named:(NSString*)name;
- (id)getObjectWithArgs:(id)classOrProtocol, ... NS_REQUIRES_NIL_TERMINATION;
- (id)getObject:(id)classOrProtocol namedWithArgs:(NSString*)name, ... NS_REQUIRES_NIL_TERMINATION;
- (id)getObject:(id)classOrProtocol arguments:(va_list)argList;
- (id)getObject:(id)classOrProtocol named:(NSString*)name arguments:(va_list)argList;
- (id)getObject:(id)classOrProtocol argumentList:(NSArray *)argumentList;
- (id)getObject:(id)classOrProtocol initializer:(SEL)selector argumentList:(NSArray *)argumentList;
- (id)getObject:(id)classOrProtocol named:(NSString*)name argumentList:(NSArray *)argumentList;
- (id)getObject:(id)classOrProtocol named:(NSString*)name initializer:(SEL)selector argumentList:(NSArray *)argumentList;

虽然方法有点多,但这些方法最后都会统一调用最后一个方法:

- (id)getObject:(id)classOrProtocol named:(NSString*)name initializer:(SEL)selector argumentList:(NSArray *)argumentList {
    @synchronized(self) {
        if (!classOrProtocol) {
            return nil;
        }
        NSString *key = nil;
        BOOL isClass = class_isMetaClass(object_getClass(classOrProtocol));
        
        if (isClass) {
            key = NSStringFromClass(classOrProtocol);
        } else {
            key = [NSString stringWithFormat:@"<%@>", NSStringFromProtocol(classOrProtocol)];
        }
        
        if (name)
        {
            key = [NSString stringWithFormat:@"%@:%@",key,name];
        }
        
        id<JSObjectionEntry> injectorEntry = [_context objectForKey:key];
        injectorEntry.injector = self;
        
        if (!injectorEntry) {
            id<JSObjectionEntry> entry = [_globalContext objectForKey:key];
            if (entry) {
                injectorEntry = [[entry class] entryWithEntry:entry];
                injectorEntry.injector = self;
                [_context setObject:injectorEntry forKey:key];
            } else if(isClass) {
                injectorEntry = [JSObjectionInjectorEntry entryWithClass:classOrProtocol scope:JSObjectionScopeNormal];
                injectorEntry.injector = self;
                [_context setObject:injectorEntry forKey:key];
            }
        }
        
        if (classOrProtocol && injectorEntry) {
            if ([injectorEntry respondsToSelector:@selector(extractObject:initializer:)]) {
                return [injectorEntry extractObject:argumentList initializer:selector];
            }
            return [injectorEntry extractObject:argumentList];
        }
        
        return nil;
    }
    
    return nil;
}

在最后一个方法中,获取实例变量的流程是这样的:
(1)根据Class或者Protocol,还有别名生成key
(2)拿着key先到context中去取数据,取到的数据是JSObjectionEntry类型的数据(因为这是一个父类),并且JSObjectionEntry实现了JSObjectionEntry协议,因此取到的数据会用id<JSObjectionEntry>这种匿名方式去接收。
(3)如果数据不存在,就到globalContext中去取(相当于二级缓存),如果取到对象,就放入context(一级缓存)中,如果没取到对象,就创建一个JSObjectionInjectorEntry对象并放入context中。
(4)如果经过(2)和(3)我们获取到了一个id<JSObjectionEntry>匿名对象,就会调用这个匿名对象的extractObject:initializer:extractObject:方法,这两个方法的具体实现在上面我们已经讲过了,在这两个方法中会创建实例对象,最终会将这个实例对象返回,我们通过IoC容器拿到的,就是这个实例对象。
(5)异常情况都会返回nil。

流程图中间的位置有一条蓝色虚线连接左右两部分,对应着上面所讲述的流程。

四、JSObjection的使用

(1)构造方法注入

定义接口:

@protocol CarDao <NSObject>

@property(nonatomic, copy) NSString *color; //汽车的颜色

- (void)fire; //启动

@end

定义接口实现类:

@interface Car : NSObject<CarDao>

@end

@implementation Car

//构造方法注入
- (instancetype)initWithColor:(NSString *)color {
    if (self = [super init]) {
        self.color = color;
    }
    return self;
}

//构造方法注入
objection_initializer(initWithColor:)

@synthesize color = _color;

- (void)fire {
    NSLog(@"我开着我%@的小汽车走了.", _color);
}

@end

绑定Class-Protocol

JSObjectionModule *module = [[JSObjectionModule alloc] init];
[module bindClass:[Car class] toProtocol:@protocol(CarDao)];
JSObjectionInjector *injector = [JSObjection createInjector:module];
[JSObjection setDefaultInjector:injector];

获取匿名对象(也就是实现了CarDao接口的Car实例对象):

id<CarDao> carDao = [[JSObjection defaultInjector] getObjectWithArgs:@protocol(CarDao), @"黑色", nil];
[carDao fire];

(2)属性注入

CarDao代码不变,对Car进行一些改造:

@implementation Car

- (instancetype)init {
    if (self = [super init]) {
        _color = @"蓝色";
    }
    
    return self;
}

@synthesize color = _color;

- (void)fire {
    NSLog(@"我开着我%@的小汽车走了.", _color);
}

@end

PersonDao

@protocol PersonDao <NSObject>

@property(nonatomic, strong) id<CarDao> car;        //汽车

@optional
- (void)info;   //个人信息

@end

Person

@interface Person : NSObject<PersonDao>

@end


@implementation Person

//属性注入
objection_requires(@"car")

@synthesize car = _car;

- (void)info {
    NSLog(@"-------------------------");
    [_car fire];
    NSLog(@"-------------------------");
}

@end

绑定及获取:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    JSObjectionModule *module = [[JSObjectionModule alloc] init];
    [module bindClass:[Person class] toProtocol:@protocol(PersonDao)];
    [module bindClass:[Car class] toProtocol:@protocol(CarDao)];

    JSObjectionInjector *injector = [JSObjection createInjector:module];
    [JSObjection setDefaultInjector:injector];
    
    //构造方法注入
    id<PersonDao> personDao = [[JSObjection defaultInjector] getObject:@protocol(PersonDao)];
    [personDao info];
}

完结。

Typhoon的补充

对于文章中提到的另一个IoC容器Typhoon,这里做一个简单的原理分析吧,Typhoon提供的功能非常接近于JavaSpring框架,包括AOP & DI & IoC

Typhooninfo.plist文件中维护了一个表,表名叫TyphoonInitialAssemblies,如下:

WX20180823-174024.png

每个item中的字符串数据都可以经过反射生成项目中的类。

这些类的名称是以Assembly为结尾的,意为部件

Assembly管理着众多的DefinitionDefinition意为定义

我们创建的类都以Assembly为结尾,表示部件(组件),在部件类中,我们会使用TyphoonDefinition中去关联要注入的Controller/view/other objc,并指定这些对象初始化所需要的初始化构造方法/属性注入/方法注入等,所以我们可以知道AssemblyDefinition是一对多关系。

在程序启动的时候首先会执行main函数:

int main(int argc, char *argv[])
{
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([PFAppDelegate class]));
    }
}

UIApplication会设置AppDelegate对象,设置会调用setAppDelegate:方法,于是Typhoonhook住了这个方法,在替换的方法实现中,会读取当前bundleinfo.plist文件,获取到TyphoonInitialAssemblies配置的内容,因为这个配置项是个数组,里面存储的是Class对应的字符串,所以获取到这些类的字符串之后就会通过反射获取到类,再将这些类放入到数组中,如果数组count>0就会通过这个数组实例化TyphoonComponentFactory对象,这个工厂对象有个私有属性_registry,里面保存的就是我们通过TyphoonDefinition注入的模块。

获取到他们的Definition之后,会将其添加到TyphoonComponentFactory(组件工厂)中,等到我们取得时候,也是到这个组件工厂中去取。

所以使用Typhoon的时候就要维护好你写的代码,因为Typhoon hook住了你当前应用程序的入口,如果在这个过程中,因为你写的代码造成了程序异常,程序可能就会无法正常启动。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容