iOS [objection]源码阅读

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>

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

推荐阅读更多精彩内容