[Edward炼金屋] MJExtension阅读笔记

写在前头:该系列仅仅只是个人的阅读笔记而已,不会做过多的讲解和分析。

知识点:

在设计上为了提高转化效率大量的使用了缓存,项目中缓存的方案有两种:

  1. 利用关联对象的方法,直接在类对象上添加关联对象。
    举个例子,作者在MJProperty类中使用了这个技巧。由于对于同一个属性objc_property_t生成过的MJProperty对象的内容都是相同的,因此作者对其进行了缓存避免重复的执行生成逻辑。作者巧妙的利用了属性property作为key。属性为一个结构体指针。类的属性的地址在编译器就决定了,而且是唯一的。它可以很好的充当key的角色。
+ (instancetype)cachedPropertyWithProperty:(objc_property_t)property
{
    MJProperty *propertyObj = objc_getAssociatedObject(self, property);
    if (propertyObj == nil) {
        propertyObj = [[self alloc] init];
        propertyObj.property = property;
        objc_setAssociatedObject(self, property, propertyObj, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    return propertyObj;
}
  1. 使用了静态字典对象,在+initialize或者+load中对静态字典对象进行初始化。
    作者用这个方案来缓存不同类的信息,这个字典的key为类名字符串,而value为其内容。
    同样举个例子,在NSObject+MJClass.h中存储了需要属性的黑白名单,白名单中的属性会进行字典和模型的转换,而黑名单中的属性则会被忽略。白名单为则表示全部参与转换。
//代码做了简化
static NSMutableDictionary *allowedPropertyNamesDict_;
static NSMutableDictionary *ignoredPropertyNamesDict_;

@implementation NSObject (MJClass)

+ (void)load
{
    allowedPropertyNamesDict_ = [NSMutableDictionary dictionary];
    ignoredPropertyNamesDict_ = [NSMutableDictionary dictionary];
}

#pragma mark - 属性白名单配置
+ (void)mj_setupAllowedPropertyNames:(MJAllowedPropertyNames)allowedPropertyNames;
{
    NSMutableArray *array = allowedPropertyNamesDict_[NSStringFromClass(self)];
    if (array) return array;
    
    // 创建、存储
    allowedPropertyNamesDict_[NSStringFromClass(self)] = array = [NSMutableArray array];
    
    ....

    return array;
    
}

+ (NSMutableArray *)mj_totalAllowedPropertyNames
{
    NSMutableArray *array = ignoredPropertyNamesDict_[NSStringFromClass(self)];
    if (array) return array;
    
    // 创建、存储
    ignoredPropertyNamesDict_[NSStringFromClass(self)] = array = [NSMutableArray array];
    
    ....

    return array;
}

@end

使用宏定义来使得类具有归档的作用

作者写了一个宏MJExtensionCodingImplementation,想要一个类支持归档,只需要在实现函数中添加这个宏即可。

@implementation MJBag
// NSCoding实现
MJExtensionCodingImplementation

@end

作者是怎么做到的呢?我们只要展开这个宏就可以知道了。这个宏帮我们实现了- (id)initWithCoder:(NSCoder *)decoder- (void)encodeWithCoder:(NSCoder *)encoder方法。在这两个方法中,分别调用了作者写的编码和解码的方法,在方法中会遍历所有的属性进行操作。

#define MJCodingImplementation \
- (id)initWithCoder:(NSCoder *)decoder \
{ \
if (self = [super init]) { \
[self mj_decode:decoder]; \
} \
return self; \
} \
\
- (void)encodeWithCoder:(NSCoder *)encoder \
{ \
[self mj_encode:encoder]; \
}

#define MJExtensionCodingImplementation MJCodingImplementation

属性的字符串描述(the attribute string of a property)

我们可以通过runtime库中const char *property_getAttributes(objc_property_t property)方法获得属性的类型描述和属性名称。我们可以使用字符串字面量方法@()将c字符串转成NSString类型。

属性的字符串描述:T + 变量类型编码 + 属性类型编码 + V + 属性名称    

官方连接:Property Type StringType Encodings

不足之处:

存在的多线程问题

MJExtension似乎并不支持多线程的转换,我在阅读源码中发现了两个多线程的问题。

  • 多线程问题1
    我构造了这么一个场景在两个线程同时对同一个模型进行转换,但是使用的字典不相同。代码如下,为了方便查看,我在 //后给出了打印结果。
- (void)testMutilThread1
{
    dispatch_async(dispatch_queue_create(NULL, NULL), ^{
        NSDictionary *dict = @{
                               @"name":@"name",
                               @"price":@(2)
                               };
        [MJBag mj_setupReplacedKeyFromPropertyName:^NSDictionary *{
            return @{
                     @"price":@"price"
                     };
        }];
        MJBag *bag =  [MJBag mj_objectWithKeyValues:dict];
        NSLog(@"%@",bag);        // name:name,price:0.000000
    });
    
    dispatch_async(dispatch_queue_create(NULL, NULL), ^{
        NSDictionary *dict = @{
                               @"name":@"name",
                               @"price2":@(3)
                               };
        [MJBag mj_setupReplacedKeyFromPropertyName:^NSDictionary *{
            return @{
                        @"price":@"price2"
                     };
        }];
        MJBag *bag =  [MJBag mj_objectWithKeyValues:dict];
        NSLog(@"%@",bag);      //name:name,price:3.000000
    });
}

我们可以看到第一个对象的结果是错误的。究其原因在于:对于同一个类的属性和字典的映射关系表在程序中只会存在一份。我们在第一个线程设置好映射关系和执行转换之间,第二个线程重新设置了映射关系,这导致了第一个字典使用了第二个字典的映射关系,从而转换错误。

  • 多线程问题2
    我们知道当我们遇到多个线程同时修改同一个字典对象的时候需要加锁。可是我在代码多个地方发现了问题,特别是对于缓存字典的操作上。我有点怀疑作者的锁加错了位置,作者只在字典的获取上加了锁,由于字典的创建在这之前已经完成,似乎多线程访问并没有问题。对于这个问题,我没有做实验,目前还处在对于代码的分析层面上。

一个模型对应多个字典效率降低的问题

之前说过作者使用字典做缓存来加快模型字典的转换效率。其中一个重要的缓存就是类的属性列表及其与字典的映射关系。作者采用字典作为缓存的容器,每一个类只有一个缓存数据。这导致当一个模型需要被用在不同的位置,需要使用不同的字典来转换,那每次转换都需要重新设置映射关系,导致缓存的失效。
更糟糕的是作者采用了一刀切的方式来清理缓存。我们来看下设置映射关系的代码。只要一个类重新设着了映射关系,那么所有的缓存都会被清理。这并不是一个聪明的做法。

+ (void)mj_setupReplacedKeyFromPropertyName:(MJReplacedKeyFromPropertyName)replacedKeyFromPropertyName
{
    //设置新的属性和字典的映射关系
    [self mj_setupBlockReturnValue:replacedKeyFromPropertyName key:&MJReplacedKeyFromPropertyNameKey];
    //清理全部的缓存
    [[self dictForKey:&MJCachedPropertiesKey] removeAllObjects];
}

疑惑

不同分类有相同的类方名,为什么不会出现冲突的情况呢?

后面查查 runtime的源码找找原因。

@implementation NSObject (MJClass)

+ (NSMutableDictionary *)dictForKey:(const void *)key
{
    @synchronized (self) {
        if (key == &MJAllowedPropertyNamesKey) return allowedPropertyNamesDict_;
        if (key == &MJIgnoredPropertyNamesKey) return ignoredPropertyNamesDict_;
        if (key == &MJAllowedCodingPropertyNamesKey) return allowedCodingPropertyNamesDict_;
        if (key == &MJIgnoredCodingPropertyNamesKey) return ignoredCodingPropertyNamesDict_;
        return nil;
    }
}

......

@end

@implementation NSObject (Property)

+ (NSMutableDictionary *)dictForKey:(const void *)key
{
    @synchronized (self) {
        if (key == &MJReplacedKeyFromPropertyNameKey) return replacedKeyFromPropertyNameDict_;
        if (key == &MJReplacedKeyFromPropertyName121Key) return replacedKeyFromPropertyName121Dict_;
        if (key == &MJNewValueFromOldValueKey) return newValueFromOldValueDict_;
        if (key == &MJObjectClassInArrayKey) return objectClassInArrayDict_;
        if (key == &MJCachedPropertiesKey) return cachedPropertiesDict_;
        return nil;
    }
}

......

@end

总结

  • MJExtension使用缓存来加快转换效率,使用了两种缓存方法:第一种是使用关联对象的方法,第二种是使用静态字典的方法;
  • MJExtension使用宏定义来使得类具有归档的作用;
  • MJExtension使用属性的字符串描述(the attribute string of a property)来得到属性的类型;
  • 建议不要在多线程的使用MJExtension,避免出现问题;
  • 尽量不要过多的调用会清理缓存的方法:如+mj_setupReplacedKeyFromPropertyName:+mj_setupReplacedKeyFromPropertyName121:等。调用这些方法会清理所有类的缓存,导致转换效率降低。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,596评论 18 139
  • 1. 简介 1.1 什么是 MyBatis ? MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的...
    笨鸟慢飞阅读 5,423评论 0 4
  • 对于从事 iOS 开发人员来说,所有的人都会答出【runtime 是运行时】什么情况下用runtime?大部分人能...
    梦夜繁星阅读 3,697评论 7 64
  • 一辈子,为了啥? 有人说,是为了名! 虽说: 好车好房,被羡慕; 有权有势,被仰慕; 名利双收,被瞩目! 但是, ...
    女王殿下1884阅读 158评论 0 0
  • 大Low_B阅读 165评论 0 0