YYModel 源码解读(二)之NSObject+YYModel.h (4)

时间:2022-09-17 22:21:30

接下来我们继续向下看

typedef struct {
void *modelMeta; ///< _YYModelMeta
void *model; ///< id (self)
void *dictionary; ///< NSDictionary (json)
} ModelSetContext;

这是一个c的结构体,在c中 void * 相当于 oc 中的 id 类型

那么 为什么使用c的结构体呢,最主要的使用场景就是我们需要同时使用多个参数的情况下,可以使用c的结构体

/**
Apply function for dictionary, to set the key-value pair to model. @param _key should not be nil, NSString.
@param _value should not be nil.
@param _context _context.modelMeta and _context.model should not be nil.
*/
static void ModelSetWithDictionaryFunction(const void *_key, const void *_value, void *_context) {
ModelSetContext *context = _context;
__unsafe_unretained _YYModelMeta *meta = (__bridge _YYModelMeta *)(context->modelMeta);
__unsafe_unretained _YYModelPropertyMeta *propertyMeta = [meta->_mapper objectForKey:(__bridge id)(_key)];
__unsafe_unretained id model = (__bridge id)(context->model);
while (propertyMeta) {
if (propertyMeta->_setter) {
ModelSetValueForProperty(model, (__bridge __unsafe_unretained id)_value, propertyMeta);
}
propertyMeta = propertyMeta->_next;
};
}

上边的代码的主要作用是 根据 一个 id 类型的_value 一个 id 类型的_key 和_context 结构体信息,社会value 到相应的属性中

/**
Apply function for model property meta, to set dictionary to model. @param _propertyMeta should not be nil, _YYModelPropertyMeta.
@param _context _context.model and _context.dictionary should not be nil.
*/
static void ModelSetWithPropertyMetaArrayFunction(const void *_propertyMeta, void *_context) {
ModelSetContext *context = _context; // 这个dictionary 是 json 字典
__unsafe_unretained NSDictionary *dictionary = (__bridge NSDictionary *)(context->dictionary);
__unsafe_unretained _YYModelPropertyMeta *propertyMeta = (__bridge _YYModelPropertyMeta *)(_propertyMeta);
if (!propertyMeta->_setter) return;
id value = nil; // 如果property 映射了 多个jsonkey
if (propertyMeta->_mappedToKeyArray) {
// 这个是之前的函数,目的是根据字典和映射的jsonkey 取出对应的值,当获取到第一个不为空的值的情况下,停止遍历
value = YYValueForMultiKeys(dictionary, propertyMeta->_mappedToKeyArray); } else if (propertyMeta->_mappedToKeyPath) { value = YYValueForKeyPath(dictionary, propertyMeta->_mappedToKeyPath);
} else {
value = [dictionary objectForKey:propertyMeta->_mappedToKey];
} if (value) {
__unsafe_unretained id model = (__bridge id)(context->model);
ModelSetValueForProperty(model, value, propertyMeta);
}
}

上边的代码也是一个赋值的函数,在一种propertyMeta 和context 的参数的情况下 ,实现的赋值方法

接下来的这个方法主要是mode to json  模型转字典的核心方法,目的是对model 进行预处理

注意,该方法只是对一个NSObject对象做预处理,并没有转JSON对象

苹果 规定使用 NSJSONSerialization 转换成JSONObject的要求:

NSArray 对象
数组元素只能是 NSString、NSNumber、NSNull
NSDictionary 对象
key 必须是 NSString
value只能是 NSString、NSNumber、NSNull
该函数对传入的NSObject对象为如下所有类型时做的预处理: NSData >>> 不能转换成JSON NSString >>> NSString NSNumber >>> NSNumber
NSURL >>> NSString
NSAttributedString >>> NSString
NSDate >>> 使用DateFormat转换成NSString NSArray 先判断NSArray是否可以被JSON化
如果可以,直接返回NSArray
如果不可以
创建一个新的NSArray
遍历之前NSArray中的每一个元素
递归将当前元素解析成 NSString、NSNumber
将解析后的元素添加到数组
返回新的数组
NSSet
NSSet >>> NSArray
走NSArray的流程
NSDictionary
类似NSArray
自定义NSObject类,非Foundation类
将当前实体类对象 >>> 使用NSDictionary对象来重新组装
属性值 >>> NSDictionary字典key-value
最终 实体类对象 >>> NSDictionary对象
/**
Returns a valid JSON object (NSArray/NSDictionary/NSString/NSNumber/NSNull),
or nil if an error occurs. @param model Model, can be nil.
@return JSON object, nil if an error occurs.
*/
/**
* 说明该方法主要使用在模型转字典的功能中
*
* @param model 模型
*
* @return 与转换后的值,因为有些值是需要处理的
*/
static id ModelToJSONObjectRecursive(NSObject *model) { // 1.kCFNull 或者 Null
if (!model || model == (id)kCFNull) return model; // 2.NSString 支持转json 直接返回该值
if ([model isKindOfClass:[NSString class]]) return model; // 3.NSNumber 支持转json 直接返回该值
if ([model isKindOfClass:[NSNumber class]]) return model; // 4.NSDictionary value 必须是NSString、NSNumber、NSNull key 必须是NSString 才支持转json 需要进行判断
if ([model isKindOfClass:[NSDictionary class]]) { // 4.1 调用NSJSONSerialization 的 isValidJSONObject: 方法 检测能否直接转json
if ([NSJSONSerialization isValidJSONObject:model]) return model; // 4.2 不能直接转json的情况,这个时候需要新建一个可变字典对象,转换成功后转换该字典
NSMutableDictionary *newDic = [NSMutableDictionary new];
[((NSDictionary *)model) enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) { // 4.2.1 对key的处理,NSDictionary的key 可以自定义,因此还有这种额外的情况
NSString *stringKey = [key isKindOfClass:[NSString class]] ? key : key.description;
if (!stringKey) return; // 4.2.2 通过递归的方式(调用自身) 预处理 字典中的value
id jsonObj = ModelToJSONObjectRecursive(obj);
if (!jsonObj) jsonObj = (id)kCFNull; // 4.2.3 使用处理过的 key value 给字典赋值
newDic[stringKey] = jsonObj;
}];
return newDic;
} // 5.集合,数组转json 跟字典一样 都要求是NSString、NSNumber、NSNull
if ([model isKindOfClass:[NSSet class]]) { // 5.1 NSSet 转换成NSArray
NSArray *array = ((NSSet *)model).allObjects; // 5.2 如果能直接转,直接返回就行了
if ([NSJSONSerialization isValidJSONObject:array]) return array; // 5.3 这里是不能直接转的情况
NSMutableArray *newArray = [NSMutableArray new];
for (id obj in array) { // 5.3.1 如果是NSString 或者 NSNumber 类型,可以直接转换
if ([obj isKindOfClass:[NSString class]] || [obj isKindOfClass:[NSNumber class]]) {
[newArray addObject:obj];
} // 5.3.2 其他的 调用自身 预处理
else {
id jsonObj = ModelToJSONObjectRecursive(obj);
if (jsonObj && jsonObj != (id)kCFNull) [newArray addObject:jsonObj];
}
}
return newArray;
} // 6. 同上 5 再次不做多余的解释
if ([model isKindOfClass:[NSArray class]]) {
if ([NSJSONSerialization isValidJSONObject:model]) return model;
NSMutableArray *newArray = [NSMutableArray new];
for (id obj in (NSArray *)model) {
if ([obj isKindOfClass:[NSString class]] || [obj isKindOfClass:[NSNumber class]]) {
[newArray addObject:obj];
} else {
id jsonObj = ModelToJSONObjectRecursive(obj);
if (jsonObj && jsonObj != (id)kCFNull) [newArray addObject:jsonObj];
}
}
return newArray;
} // 7. 对 NSURL 转成NSString
if ([model isKindOfClass:[NSURL class]]) return ((NSURL *)model).absoluteString; // 8. 对NSAttributedString 转成NSString
if ([model isKindOfClass:[NSAttributedString class]]) return ((NSAttributedString *)model).string; // 9. 对NSDate 转成NSString
if ([model isKindOfClass:[NSDate class]]) return [YYISODateFormatter() stringFromDate:(id)model]; // 10. 对NSData 不做处理,直接返回nil
if ([model isKindOfClass:[NSData class]]) return nil; // 11. 如果不是上边的类型,就说明使用了自定义的类型。需要把自定义的类型转成NSDictionary 处理 // 11.1 根据类型Model 初始化一个_YYModelMeta
_YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:[model class]];
if (!modelMeta || modelMeta->_keyMappedCount == ) return nil; // 11.2 新建一个数组,使用__unsafe_unretained 来避免 在block 中的 retain 和 release
NSMutableDictionary *result = [[NSMutableDictionary alloc] initWithCapacity:];
__unsafe_unretained NSMutableDictionary *dic = result; // avoid retain and release in block // 11.3 遍历modelMeta->_mapper 这个字典 得到 jsonkey 和 _YYModelPropertyMeta描述
[modelMeta->_mapper enumerateKeysAndObjectsUsingBlock:^(NSString *propertyMappedKey, _YYModelPropertyMeta *propertyMeta, BOOL *stop) { // 11.3.1 判空
if (!propertyMeta->_getter) return; // 11.3.2 value 用来接收转换后的值
id value = nil; // 11.3.3 如果是_isCNumber
if (propertyMeta->_isCNumber) { // 11.3.3.1 通过调用ModelCreateNumberFromProperty 函数 获得转换后的NSNumber值
value = ModelCreateNumberFromProperty(model, propertyMeta);
}
// 11.3.4 如果是_nsType 类型
else if (propertyMeta->_nsType) { // 11.3.4.1 转换
id v = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter);
value = ModelToJSONObjectRecursive(v);
}
// 11.3.5 其他的情况
else { // 11.3.5.1 判断propertyMeta->_type 根据不同的类型转换成NSString 类型
switch (propertyMeta->_type & YYEncodingTypeMask) {
case YYEncodingTypeObject: {
id v = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter);
value = ModelToJSONObjectRecursive(v);
if (value == (id)kCFNull) value = nil;
} break;
case YYEncodingTypeClass: {
Class v = ((Class (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter);
value = v ? NSStringFromClass(v) : nil;
} break;
case YYEncodingTypeSEL: {
SEL v = ((SEL (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter);
value = v ? NSStringFromSelector(v) : nil;
} break;
default: break;
}
}
if (!value) return; // 到此 我们 已经获取到了 转换成功后的 value 了
// 11.3.6 处理keypath 的情况
/**
* 将一个实体类对象属性值,按照json keypath,转换成对应的字典
* @"AA" --映射-- @"user.name"
*
* 还原成
*
* @{
@"id" : 20 ,
* @"user" : @{
* @"name" : @"AA",
* }
* }
*/
if (propertyMeta->_mappedToKeyPath) {
NSMutableDictionary *superDic = dic;
NSMutableDictionary *subDic = nil; // 便利keypath数组 上边的例子中 [@"user",@"name"]
for (NSUInteger i = , max = propertyMeta->_mappedToKeyPath.count; i < max; i++) { // 11.3.6.1 取出数组中国的key
NSString *key = propertyMeta->_mappedToKeyPath[i]; // 11.3.6.2 如果便利到最后一个的时候,对
if (i + == max) { // end
if (!superDic[key]) superDic[key] = value;
break;
} // 11.3.6.3 给最上边的父类 赋值 上边的例子中dic[@"user"] == subDic
subDic = superDic[key];
if (subDic) {
if ([subDic isKindOfClass:[NSDictionary class]]) {
subDic = subDic.mutableCopy;
superDic[key] = subDic;
} else {
break;
}
} else {
subDic = [NSMutableDictionary new];
superDic[key] = subDic;
}
superDic = subDic;
subDic = nil; /*
总之,上边这个循环的处理目的就是给字典中keypath 映射的模型中的属性赋值
如果 映射的模型中的该属性有值就不赋值了
*/
}
} else {
if (!dic[propertyMeta->_mappedToKey]) {
dic[propertyMeta->_mappedToKey] = value;
}
}
}]; // 11.4 判断该类是否实现了modelCustomTransformToDictionary 方法
if (modelMeta->_hasCustomTransformToDictionary) {
BOOL suc = [((id<YYModel>)model) modelCustomTransformToDictionary:dic];
if (!suc) return nil;
}
return result;
}

下边的代码是控制行的缩进

NSMutableString *str = 可变的@"xxxxxxxxxxxxxxxxxxxxxxxx\nxxxxxxxxxxxxxxxxxxxxxxxx\nxxxxxxxxxxxxxxxxxxxxxxxx";

显示是这样的

xxxxxxxxxxxxxxxxxxxxxxxx

xxxxxxxxxxxxxxxxxxxxxxxx

xxxxxxxxxxxxxxxxxxxxxxxx

ModelDescriptionAddIndent(str,1) 的结果就是这样的

xxxxxxxxxxxxxxxxxxxxxxxx

xxxxxxxxxxxxxxxxxxxxxxxx

xxxxxxxxxxxxxxxxxxxxxxxx

/// Add indent to string (exclude first line)
// 这个函数的目的就是除了第一行,其他的行都缩进 indent * 4 个 字符
static NSMutableString *ModelDescriptionAddIndent(NSMutableString *desc, NSUInteger indent) {
for (NSUInteger i = , max = desc.length; i < max; i++) {
unichar c = [desc characterAtIndex:i];
if (c == '\n') {
for (NSUInteger j = ; j < indent; j++) {
[desc insertString:@" " atIndex:i + ];
}
i += indent * ;
max += indent * ;
}
}
return desc;
}

类似系统的获取对这个模型的描述

/// Generate a description string
static NSString *ModelDescription(NSObject *model) { // 1. 设置描述的最大长度为100
static const int kDescMaxLength = ; // 2. 进行判空或者不是NSObject的处理
if (!model) return @"<nil>";
if (model == (id)kCFNull) return @"<null>";
if (![model isKindOfClass:[NSObject class]]) return [NSString stringWithFormat:@"%@",model]; // 3. 获取modelMeta的抽象类
_YYModelMeta *modelMeta = [_YYModelMeta metaWithClass:model.class]; // 3.1 使用抽象类的类型
switch (modelMeta->_nsType) { // 3.1.1 字符串
case YYEncodingTypeNSString: case YYEncodingTypeNSMutableString: {
return [NSString stringWithFormat:@"\"%@\"",model];
} // 3.1.2 NSVale 或者NSData
case YYEncodingTypeNSValue:
case YYEncodingTypeNSData: case YYEncodingTypeNSMutableData: {
NSString *tmp = model.description; // 处理超过最大描述长度
if (tmp.length > kDescMaxLength) {
tmp = [tmp substringToIndex:kDescMaxLength];
tmp = [tmp stringByAppendingString:@"..."];
}
return tmp;
} // 3.1.3 NSNumber NSDate NSURL
case YYEncodingTypeNSNumber:
case YYEncodingTypeNSDecimalNumber:
case YYEncodingTypeNSDate:
case YYEncodingTypeNSURL: {
return [NSString stringWithFormat:@"%@",model];
} // 3.1.4 NSSet
case YYEncodingTypeNSSet: case YYEncodingTypeNSMutableSet: {
model = ((NSSet *)model).allObjects;
} // no break // 3.1.5 NSArray
case YYEncodingTypeNSArray: case YYEncodingTypeNSMutableArray: {
NSArray *array = (id)model; // 便利数组,逐行打印数组内部的类型
NSMutableString *desc = [NSMutableString new];
if (array.count == ) {
return [desc stringByAppendingString:@"[]"];
} else {
[desc appendFormat:@"[\n"];
for (NSUInteger i = , max = array.count; i < max; i++) {
NSObject *obj = array[i];
[desc appendString:@" "];
[desc appendString:ModelDescriptionAddIndent(ModelDescription(obj).mutableCopy, )];
[desc appendString:(i + == max) ? @"\n" : @";\n"];
}
[desc appendString:@"]"];
return desc;
}
} // 3.1.6 NSDictionary
case YYEncodingTypeNSDictionary: case YYEncodingTypeNSMutableDictionary: {
NSDictionary *dic = (id)model; // 便利字典,逐行打印字典的key value
NSMutableString *desc = [NSMutableString new];
if (dic.count == ) {
return [desc stringByAppendingString:@"{}"];
} else {
NSArray *keys = dic.allKeys; [desc appendFormat:@"{\n"];
for (NSUInteger i = , max = keys.count; i < max; i++) {
NSString *key = keys[i];
NSObject *value = dic[key];
[desc appendString:@" "];
[desc appendFormat:@"%@ = %@",key, ModelDescriptionAddIndent(ModelDescription(value).mutableCopy, )];
[desc appendString:(i + == max) ? @"\n" : @";\n"];
}
[desc appendString:@"}"];
}
return desc;
} // 3.1.7 自定义的类型
default: { // 如果没有属性,则打印内存地址
NSMutableString *desc = [NSMutableString new];
[desc appendFormat:@"<%@: %p>", model.class, model];
if (modelMeta->_allPropertyMetas.count == ) return desc; // sort property names 按名称排序
NSArray *properties = [modelMeta->_allPropertyMetas
sortedArrayUsingComparator:^NSComparisonResult(_YYModelPropertyMeta *p1, _YYModelPropertyMeta *p2) {
return [p1->_name compare:p2->_name];
}]; [desc appendFormat:@" {\n"];
for (NSUInteger i = , max = properties.count; i < max; i++) {
_YYModelPropertyMeta *property = properties[i];
NSString *propertyDesc;
if (property->_isCNumber) {
NSNumber *num = ModelCreateNumberFromProperty(model, property);
propertyDesc = num.stringValue;
} else {
switch (property->_type & YYEncodingTypeMask) {
case YYEncodingTypeObject: {
id v = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, property->_getter);
propertyDesc = ModelDescription(v);
if (!propertyDesc) propertyDesc = @"<nil>";
} break;
case YYEncodingTypeClass: {
id v = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, property->_getter);
propertyDesc = ((NSObject *)v).description;
if (!propertyDesc) propertyDesc = @"<nil>";
} break;
case YYEncodingTypeSEL: {
SEL sel = ((SEL (*)(id, SEL))(void *) objc_msgSend)((id)model, property->_getter);
if (sel) propertyDesc = NSStringFromSelector(sel);
else propertyDesc = @"<NULL>";
} break;
case YYEncodingTypeBlock: {
id block = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, property->_getter);
propertyDesc = block ? ((NSObject *)block).description : @"<nil>";
} break;
case YYEncodingTypeCArray: case YYEncodingTypeCString: case YYEncodingTypePointer: {
void *pointer = ((void* (*)(id, SEL))(void *) objc_msgSend)((id)model, property->_getter);
propertyDesc = [NSString stringWithFormat:@"%p",pointer];
} break;
case YYEncodingTypeStruct: case YYEncodingTypeUnion: {
NSValue *value = [model valueForKey:property->_name];
propertyDesc = value ? value.description : @"{unknown}";
} break;
default: propertyDesc = @"<unknown>";
}
} propertyDesc = ModelDescriptionAddIndent(propertyDesc.mutableCopy, );
[desc appendFormat:@" %@ = %@",property->_name, propertyDesc];
[desc appendString:(i + == max) ? @"\n" : @";\n"];
}
[desc appendFormat:@"}"];
return desc;
}
}
}

YYModel 源码解读(二)之NSObject+YYModel.h (4)的更多相关文章

  1. YYModel 源码解读(二)之NSObject&plus;YYModel&period;h &lpar;1&rpar;

    本篇文章主要介绍 _YYModelPropertyMeta 前边的内容 首先先解释一下前边的辅助函数和枚举变量,在写一个功能的时候,这些辅助的东西可能不是一开始就能想出来的,应该是在后续的编码过程中 ...

  2. jQuery&period;Callbacks 源码解读二

    一.参数标记 /* * once: 确保回调列表仅只fire一次 * unique: 在执行add操作中,确保回调列表中不存在重复的回调 * stopOnFalse: 当执行回调返回值为false,则 ...

  3. &lpar;转&rpar;go语言nsq源码解读二 nsqlookupd、nsqd与nsqadmin

    转自:http://www.baiyuxiong.com/?p=886 ---------------------------------------------------------------- ...

  4. YYModel 源码解读(二)之YYClassInfo&period;h &lpar;3&rpar;

    前边3篇介绍了YYClassinfo 文件的组成单元,算是功能的分割,按照业务的设计思想来说,方向应该是相反的 由此引申出我们在设计api的思想其实和项目管理是很类似的----- 一些题外话 1.目的 ...

  5. YYModel 源码解读 总结

    在使用swfit写代码的过程中,使用了下oc写的字典转模型,发现有些属性转不成功,就萌生了阅读源码的想法. 其实一直都知道Runtime机制,但并没有系统的学习,可能是因为平时的使用比较少,无意间在g ...

  6. YYModel 源码解读(一)之YYModel&period;h

    #if __has_include(<YYModel/YYModel.h>) FOUNDATION_EXPORT double YYModelVersionNumber; FOUNDATI ...

  7. mybatis源码解读&lpar;二&rpar;——构建Configuration对象

    Configuration 对象保存了所有mybatis的配置信息,主要包括: ①. mybatis-configuration.xml 基础配置文件 ②. mapper.xml 映射器配置文件 1. ...

  8. ConcurrentHashMap源码解读二

    接下来就讲解put里面的三个方法,分别是 1.数组初始化方法initTable() 2.线程协助扩容方法helpTransfer() 3.计数方法addCount() 首先是数组初始化,再将源码之前, ...

  9. go语言nsq源码解读二 nsqlookupd、nsqd与nsqadmin

    nsqlookupd: 官方文档解释见:http://bitly.github.io/nsq/components/nsqlookupd.html 用官方话来讲是:nsqlookupd管理拓扑信息,客 ...

  10. vue2&period;0 源码解读&lpar;二&rpar;

    小伞最近比较忙,阅读源码的速度越来越慢了 最近和朋友交流的时候,发现他们对于源码的目录结构都不是很清楚 红色圈子内是我们需要关心的地方 compiler  模板编译部分 core 核心实现部分 ent ...

随机推荐

  1. CREATE TABLE 表名 AS SELECT 语句

    1.新表不存在复制表结构即数据到新表 ? 1 2 create table new_table select * from old_talbe; 这种方法会将old_table中所有的内容都拷贝过来, ...

  2. html和css书写规范

    HTML 规范 分离的标记.样式和脚本 结构.表现.行为分离 在可能情况下验证你的标记 使用编辑器验证你的标记是否正确,一般编辑器都自带有这个功能. 技术不支持的时候使用备胎,如canvas 编码格式 ...

  3. intellij代码跳转后跳回

    跳转快捷键: ctrl+b 跳回的快捷键默认为 ctrl+alt+left 然而在我的电脑上并没有卵用,所以自己设置回退的快捷键,设置位置为: File/Setting/Keymap 选择 Main ...

  4. 【WinForm】C&num; 发送Email

    发送Email  的条件 1.SmtpClient SMTP 协议    即 Host 处理事务的主机或IP地址     //smtp.163.com      UseDefaultCredentia ...

  5. struts2学生信息管理系统篇章④

    把util包中的代码做了一下注释,分享一下 DateUtil 类 package com.java1234.util; import java.text.SimpleDateFormat; impor ...

  6. Object-c学习之路十一(NSDate和反射)

    挺简单啥也不说了直接上代码(NSDate和反射) // // main.m // NSNumberAndNSValue // // Created by WildCat on 13-7-26. // ...

  7. SUSE 11下安装DPDK

    SUSE下安装与centos下安装有稍许不同: # cd  dpdk-1.8.0 # grep -rn Werror . |grep -iE "Makefile|mk" |awk ...

  8. RabbitMQ 发布订阅持久化

    RabbitMQ是一种重要的消息队列中间件,在生产环境中,稳定是第一考虑.RabbitMQ厂家也深知开发者的声音,稳定.可靠是第一考虑,为了消息传输的可靠性传输,RabbitMQ提供了多种途径的消息持 ...

  9. python sorted函数多条件排序是怎么回事

    首先,要知道sorted 内部实现使用了归并排序,而归并排序是稳定的排序,就是说当元素比不出大小时,其相对位置是不变的. 那么,利用稳定排序的特性,key函数有几个返回值就排序几次,先排序次要条件,后 ...

  10. vs2015打开Dialog出现HRESULT:0x8CE0000B

    关闭项目  在工程目录找到.vc.db文件删除