美文网首页
iOS [objection]源码阅读

iOS [objection]源码阅读

作者: 杨柳小易 | 来源:发表于2017-02-24 13:54 被阅读683次

    objection 源码阅读

    本篇主要分析,property的注入过程

    注入<code>property</code>的方式主要两种。都是通过使用神奇的宏

    objection_requires(@"engine", @"brakes")
    objection_requires_sel(@selector(engine), @selector(brakes))
    
    

    这个两个宏,区别不大,下面看看栗子

    分析栗子代码如下:例子来自git官网,稍有改动。

    
    @class Engine, Brakes;
    @interface Car : NSObject
    // Will be filled in by objection
    @property(nonatomic, strong) Engine *engine;
    // Will be filled in by objection
    @property(nonatomic, strong) Brakes *brakes;
    @property(nonatomic) BOOL awake;
    @end
    
    @implementation Car
    objection_requires(@"engine", @"brakes")
    @synthesize engine, brakes, awake;
    
    - (id)init {
        self = [super init];
        if (self) {
            [[JSObjection defaultInjector] injectDependencies:self];
        }
        return self;
    }
    
    @end
    
    @interface Engine : NSObject
    @property (nonnull, copy) NSString *name;
    @end
    
    @implementation Engine
    - (id)init {
        self = [super init];
        if (self) {
            _name = @"YLJ";
        }
        return self;
    }
    
    @end
    
    @interface Brakes : NSObject
    @property (nonatomic, assign) NSUInteger age;
    @end
    
    @implementation Brakes
    
    - (id)init {
        self = [super init];
        if (self) {
            _age = 100;
        }
        return self;
    }
    
    - (void)awakeFromObjection {
        NSLog(@"11111");
        //在这里,可以观察,对象的创建时机,
        //可以做一些初始化动作。此时对象已经创建好
    }
    
    @end
    
    

    engine和brakes加上age和name有助于观察是否注入成功。
    然后我们像平常new对象那样创建 Car 对象,

    Car *c = [Car new];
    

    观察<code> engine </code> 和 <code> brakes </code>的name和age值。

    objection_requires(@"engine", @"brakes");
    

    注入<code>property</code>的过程详解。

    <code> objection_requires </code>是一个宏。主要作用是 生成一个<code>NSSet *</code>来说明有哪些属性要注入。

    #define objection_requires(args...) \
        + (NSSet *)objectionRequires { \
            NSSet *requirements = [NSSet setWithObjects: args, nil]; \
            return JSObjectionUtils.buildDependenciesForClass(self, requirements); \
        }
    
    

    在 <code>@implementation Car</code>后面加上宏,相当于,给类注入了一个函数,<code> objectionRequires </code>.卖个关子,这个函数以后有用。

    如果想通过正常的对象创建方式来创建对象就要在合适的时机加上注入的过程,比如:

    - (id)init {
        self = [super init];
        if (self) {
        //就会去创建刚在注入的 property
            [[JSObjection defaultInjector] injectDependencies:self];
        }
        return self;
    }
    
    

    我们可以在<code>awakeFromObjection</code>函数里打断点,看看调用过程,如下图所示:

    property的注入调用

    接下来一步步分析。

    - (void)injectDependencies:(id)object {
    JSObjectionUtils.injectDependenciesIntoProperties(self, [object class], object);
    }
    
    

    此函数的作用是看看Class有没有需要注入的<code>Properties</code>第一步那个神秘的宏排上用场了。

    <code>JSObjectionUtils</code>是一个const 的结构体,里面包含了一些静态函数,此处,相当于作用域的概念,

    平时写代码,可以学习借鉴 JSObjectionUtils 不用class 组合一个 命名空间

    ///Properties 的注入过程
    static void InjectDependenciesIntoProperties(JSObjectionInjector *injector, Class klass, id object) {
       //查看Class 有没有要注入的 Properties,objectionRequires 是我们最开始那个宏,给class 动态注入的函数,返回一个,NSSet(看有哪些属性,需要注入)
      if ([klass respondsToSelector:@selector(objectionRequires)]) {
            NSSet *properties = [klass performSelector:@selector(objectionRequires)];
            NSMutableDictionary *propertiesDictionary = [NSMutableDictionary dictionaryWithCapacity:properties.count];
            for (NSString *propertyName in properties) {
                JSObjectionPropertyInfo propertyInfo;
                id desiredClassOrProtocol;
                // 获取<code>Property</code>的信息,
                _getPropertyInfo(klass, propertyName, &propertyInfo, &desiredClassOrProtocol);
                //获取一个对象,对象的 alloc init 方法就是在这里被调用的
                id theObject = [injector getObject:desiredClassOrProtocol];
                //验证获取的到的对象值是否是有效的,安全措施
                _validateObjectReturnedFromInjector(&theObject, propertyInfo, desiredClassOrProtocol, propertyName);
                [propertiesDictionary setObject:theObject forKey:propertyName];
            }
            // 设置对象的值
            [object setValuesForKeysWithDictionary:propertiesDictionary];
        }
        
        ///干扰项,删除
        ······
        ///
        
        ///对象生成成功,如果对象实现了 awakeFromObjection 函数,则去调用。
        if ([object respondsToSelector:@selector(awakeFromObjection)]) {
            [object performSelector:@selector(awakeFromObjection)];
        }
    }
    
    

    通过以上代码可以看出,注入<code>Properties</code>的过程就是

    • 看类有没有实现objectionRequires方法
    • 获取要注入的<code>Properties</code> 的信息
    • 通过<code>JSObjectionInjector</code>的 <code>getObject</code>方法,生成要注入的对象,

    新生成的对象,依然会重复上面注入过程,知道对象里没有需要注入的内容为止。

    • 验证对象是否合法
    • 然后通过<code>setValuesForKeysWithDictionary</code>方法,设置Property的值。

    当然通过JSObjectionInjector 获取对象是一个复杂的过程,有空了继续分析。上面分析的生成对象如下代码。最后会递归的查询,还有没有对象要注入。

    - (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;
    }
    

    重点就在于以下几个函数

    NSString *attributes = [NSString stringWithCString: property_getAttributes(property) encoding: NSASCIIStringEncoding];  
    
    - (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues;
    

    还有

    objection_requires
    

    <code>objection_initializer</code>宏用来指定默认的构造函数
    不指定构造的函数的时候,默认调用<code>alloc init</code>方法,指定方式如下:

    @implementation Engine
    
    objection_initializer(initWithName:,@"XXX")
    
    - (id)initWithName:(NSString *)name {
        self = [super init];
        if (self) {
            _name = name;
        }
        return self;
    }
    
    

    上面为 <code> Engine </code>注入到其他类指定了默认的方法<code> initWithName: </code> 并且有默认的参数 <code>XXX</code>

    实现原理:

    objectionInitializer 给类添加了默认的 方法 objectionInitializer 返回一个字典,包含了方法名 和 参数 。在 <code>- (id)buildObject:(NSArray *)arguments initializer: (SEL) initializer </code>方法里会判断类有没有实现objectionInitializer 方法,如果实现了,就会通过<code>NSInvocation</code>调用提供的默认构造方法。

    <strong>当然通过<code>objection_initializer</code> 指定默认的注入构造函数,也可以带自定义的参数,不然这个 <code>objection_initializer</code> 多么鸡肋</strong>

    可以这样子传参数

    JSObjectionInjector *injector = [JSObjection createInjector];
        [JSObjection setDefaultInjector:injector];
        
        Engine *e = [injector getObjectWithArgs:[Engine class], @"Test", nil];
        NSLog(@"%@",e.name);
    

    这样子获取到的对象<code>e</code>就的属性 <code>name</code> 值就是<code>Test</code>

    这个过程没什么可以分析的,跟之前的基本一样。

    <strong><code>objection_initializer</code>宏也可以用来指定类方法为获取注入对象的方法。</strong>/n比如

    @implementation Engine
    
    objection_initializer(EngineFactory)
    
    + (id)EngineFactory {
        Engine *e = [Engine new];
        e.name = @"xxx";
        return e;
    }
    

    调用的时候就不用传参数了

    之所以能做到,是通过以下方式实现的。

    static id BuildObjectWithInitializer(Class klass, SEL initializer, NSArray *arguments) {
    /// 注意,这是直接取 Class的方法签名。下面才能判断是不是类方法
        NSMethodSignature *signature = [klass methodSignatureForSelector:initializer];
        __autoreleasing id instance = nil;
        ///查看是不是类方法
        BOOL isClassMethod = signature != nil && initializer != @selector(init);
        ///如果不是类方法,尝试获取实例的方法签名。
        if (!isClassMethod) {
            instance = [klass alloc];
            signature = [klass instanceMethodSignatureForSelector:initializer];
        }
        
        if (signature) {
        /// 如果方法存在,调用方法,
            NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
            [invocation setTarget:isClassMethod ? klass : instance];
            [invocation setSelector:initializer];
            /// 传参数
            for (int i = 0; i < arguments.count; i++) {
                __unsafe_unretained id argument = [arguments objectAtIndex:i];
                [invocation setArgument:&argument atIndex:i + 2];
            }
            /// 方法调用
            [invocation invoke];
            [invocation getReturnValue:&instance];
            return instance;
        } else {
            @throw [NSException exceptionWithName:JSObjectionException reason:[NSString stringWithFormat:@"Could not find initializer '%@' on %@", NSStringFromSelector(initializer), NSStringFromClass(klass)] userInfo:nil]; 
        }
        return nil;
    }
    
    

    如上图注释所示,重点就是最开始使用 Class 去看有没有方法签名
    <code>NSMethodSignature *signature = [klass methodSignatureForSelector:initializer];</code>

    相关文章

      网友评论

          本文标题:iOS [objection]源码阅读

          本文链接:https://www.haomeiwen.com/subject/xslwwttx.html