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];
}
网友评论