美文网首页
MixPanel代码阅读笔记-对象序列化

MixPanel代码阅读笔记-对象序列化

作者: 云舒卷_js | 来源:发表于2018-03-21 18:44 被阅读0次

Web端如何知道哪些控件是可以交互,以及这些控件的布局约束的呢?那就需要我们提供准确的数据了。

Mixpanel开发了一套屏幕序列化机制,把当前的屏幕从window开始逐层序列化,对所有的可视化元素当成对象处理,把对象的属性按照一定的格式组织起来,以json的形式发给web端,web端拿到json后按照预先定好的规则反序列,绘制出可视化元素和拼接path出来。

按照它的代码逻辑,可以将序列化操作看做由4部分构成:

Description描述类:类型描述,类描述,属性描述,枚举描述

Serializer序列化:MPApplicationStateSerializer和MPObjectSerializer

上下文:MPObjectSerializerContext 未访问视图队列和已访问视图队列

配置文件:MPObjectSerializerConfig 从web端获取json解析的类

重要代码部分

从window开始做序列化,序列化部分我分成2个目标来获取元素属性

- (NSDictionary *)objectHierarchyForWindowAtIndex:(NSUInteger)index

{

    UIWindow *window = [self windowAtIndex:index];

    if (window) {

        return [_serializer serializedObjectsWithRootObject:window];

    }

    return @{};

}

这时候创建上下文,把window放入未访问队列 _unvisitedObjects = [NSMutableSet setWithObject:object];

对传来的window做第一次序列化处理,从未访问队列里取出来window执行

visitObject:withContext:方法

- (NSDictionary *)serializedObjectsWithRootObject:(id)rootObject

{

    NSParameterAssert(rootObject != nil);

    MPObjectSerializerContext *context = [[MPObjectSerializerContext alloc] initWithRootObject:rootObject];

    while ([context hasUnvisitedObjects])

    {

        [self visitObject:[context dequeueUnvisitedObject] withContext:context];

    }

    return @{

            @"objects": [context allSerializedObjects],

            @"rootObject": [_objectIdentityProvider identifierForObject:rootObject]

    };

}

将object(第一次是window)放入已经访问队列里,拿到对象后开始我们第一部分目标:在这个方法中,我们想要拿到一个元素的完整属性(id,class,properties,delegate),id是当前序列化话对象index,计数从1开始,按照顺序排列。class 遍历了该元素所有的父类。delegate包括代理的类class和代理方法selectors。properties是元素的详细属性信息,包括了可否交互,frame,subviews等重要信息。

- (void)visitObject:(NSObject *)object withContext:(MPObjectSerializerContext *)context

{

    NSParameterAssert(object != nil);

    NSParameterAssert(context != nil);

    [context addVisitedObject:object];

    NSMutableDictionary *propertyValues = [NSMutableDictionary dictionary];

    MPClassDescription *classDescription = [self classDescriptionForObject:object];

    if (classDescription) {

        for (MPPropertyDescription *propertyDescription in [classDescription propertyDescriptions]) {

            if ([propertyDescription shouldReadPropertyValueForObject:object]) {

                id propertyValue = [self propertyValueForObject:object withPropertyDescription:propertyDescription context:context];

                propertyValues[propertyDescription.name] = propertyValue ?: [NSNull null];

            }

        }

    }

    NSMutableArray *delegateMethods = [NSMutableArray array];

    id delegate;

    SEL delegateSelector = @selector(delegate);

    if ([classDescription delegateInfos].count > 0 && [object respondsToSelector:delegateSelector]) {

        delegate = ((id (*)(id, SEL))[object methodForSelector:delegateSelector])(object, delegateSelector);

        for (MPDelegateInfo *delegateInfo in [classDescription delegateInfos]) {

            if ([delegate respondsToSelector:NSSelectorFromString(delegateInfo.selectorName)]) {

                [delegateMethods addObject:delegateInfo.selectorName];

            }

        }

    }

    NSDictionary *serializedObject = @{

        @"id": [_objectIdentityProvider identifierForObject:object],

        @"class": [self classHierarchyArrayForObject:object],

        @"properties": propertyValues,

        @"delegate": @{

                @"class": delegate ? NSStringFromClass([delegate class]) : @"",

                @"selectors": delegateMethods

            }

    };

    [context addSerializedObject:serializedObject];

}

获取property属性的方案:

1.KVC 快速获取元素对象属性

2.使用Invocation 发送消息,mixpanel重写了部分方法名来获取特征值方法名有mp_varA,mp_varB,mp_varC,mp_varD,mp_varE,mp_returnValue返回特征值。

- (id)propertyValueForObject:(NSObject *)object withPropertyDescription:(MPPropertyDescription *)propertyDescription context:(MPObjectSerializerContext *)context

{

    NSMutableArray *values = [NSMutableArray array];

    MPPropertySelectorDescription *selectorDescription = propertyDescription.getSelectorDescription;

    if (propertyDescription.useKeyValueCoding) {

        // the "fast" (also also simple) path is to use KVC

        id valueForKey = [object valueForKey:selectorDescription.selectorName];

        id value = [self propertyValue:valueForKey

                  propertyDescription:propertyDescription

                              context:context];

        NSDictionary *valueDictionary = @{

                @"value": (value ?: [NSNull null])

        };

        [values addObject:valueDictionary];

    }

    else if (propertyDescription.useInstanceVariableAccess)

    {

        id valueForIvar = [self instanceVariableValueForObject:object propertyDescription:propertyDescription];

        id value = [self propertyValue:valueForIvar

                  propertyDescription:propertyDescription

                              context:context];

        NSDictionary *valueDictionary = @{

            @"value": (value ?: [NSNull null])

        };

        [values addObject:valueDictionary];

    } else {

        // the "slow" NSInvocation path. Required in order to invoke methods that take parameters.

        NSInvocation *invocation = [self invocationForObject:object withSelectorDescription:selectorDescription];

        if (invocation) {

            NSArray *parameterVariations = [self parameterVariationsForPropertySelector:selectorDescription];

            for (NSArray *parameters in parameterVariations) {

                [invocation mp_setArgumentsFromArray:parameters];

                [invocation invokeWithTarget:object];

                id returnValue = [invocation mp_returnValue];

                id value = [self propertyValue:returnValue

                          propertyDescription:propertyDescription

                                      context:context];

                NSDictionary *valueDictionary = @{

                    @"where": @{ @"parameters": parameters },

                    @"value": (value ?: [NSNull null])

                };

                [values addObject:valueDictionary];

            }

        }

    }

    return @{@"values": values};

}

遍历属性的过程中,遇到属性类别是NSArray或者是NSSet,propertyValue isKindOfClass:[NSArray class]|| [propertyValue isKindOfClass:[NSSet class],则这我们需要的到的第二目标subviews,把子控件扔到未访问视图队列 [context enqueueUnvisitedObject:value];等到序列它父类完成后从头(第一部分目标)开始循环反复。。。

- (id)propertyValue:(id)propertyValue propertyDescription:(MPPropertyDescription *)propertyDescription context:(MPObjectSerializerContext *)context

{

    if (propertyValue != nil) {

        if ([context isVisitedObject:propertyValue]) {

            return [_objectIdentityProvider identifierForObject:propertyValue];

        }

        else if ([self isNestedObjectType:propertyDescription.type])

        {

            [context enqueueUnvisitedObject:propertyValue];

            return [_objectIdentityProvider identifierForObject:propertyValue];

        }

        else if ([propertyValue isKindOfClass:[NSArray class]] || [propertyValue isKindOfClass:[NSSet class]])

        {

            NSMutableArray *arrayOfIdentifiers = [NSMutableArray array];

            for (id value in propertyValue) {

                if ([context isVisitedObject:value] == NO) {

                    [context enqueueUnvisitedObject:value];

                }

                [arrayOfIdentifiers addObject:[_objectIdentityProvider identifierForObject:value]];

            }

            propertyValue = [arrayOfIdentifiers copy];

        }

    }

    return [propertyDescription.valueTransformer transformedValue:propertyValue];

}

相关文章

网友评论

      本文标题:MixPanel代码阅读笔记-对象序列化

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