DKNightVersion源码阅读笔记

DKNightVersion是git上一款优秀的切换主题的第三方库,如图所示,你可以自定义主题,自由的切换文字&背景&图片的颜色与内容,以下是我的学习笔记.

image.png

地址:DKNightVersionDemo

结构

image.png

流程

1. 如何设置默认主题

image.png

以控件UIBarButtonItem为例

UIBarButtonItem *normalItem = [[UIBarButtonItem alloc] initWithTitle:@"Normal" style:UIBarButtonItemStylePlain target:self action:@selector(normal)];
normalItem.dk_tintColorPicker = DKColorPickerWithKey(TINT);//TINT是设置batButtonItem的tint属性的意思

1.1 创建一个DKNightVersion,然后设定有哪些基本要切换的主题

//初始化DKNightVersion,把它变成一个单例,给定基本主题
+ (instancetype)sharedInstance {
    static DKNightVersion *sharedInstance = nil;
    static dispatch_once_t oncePredicate;
    dispatch_once(&oncePredicate, ^{
        sharedInstance = [[self alloc] init];
        sharedInstance.themes = @[DKThemeVersionNormal, DKThemeVersionNight];
    });
    return sharedInstance;
}

1.2 DKColorTable会解析resource,得到对应主题的对应颜色等信息

//通过宏定义调取方法
#define DKColorPickerWithKey(key) [[DKColorTable sharedColorTable] pickerWithKey:@#key]
+ (instancetype)sharedColorTable {
    static DKColorTable *sharedInstance = nil;
    static dispatch_once_t oncePredicate;
    dispatch_once(&oncePredicate, ^{
        sharedInstance = [[DKColorTable alloc] init];
        sharedInstance.themes = DKNightVersion.themes;
        sharedInstance.file = @"DKColorTable.plist";//通过setFile方法解析配置文件
    });
    return sharedInstance;
}

- (void)setFile:(NSString *)file {
    _file = file;
    [self reloadColorTable];
}
//源码提供了2中解析方式,一种是plist,一种是txt
- (void)reloadColorTable {
    // Clear previos color table
    self.table = nil;

    NSString *pathExtension = self.file.pathExtension;

    if ([pathExtension isEqualToString:@"plist"]) {
        [self loadFromPlist];
    } else if ([pathExtension isEqualToString:@"txt"] || [pathExtension isEqualToString:@""]) {
        [self loadFromPlainText];
    } else {
        NSAssert(NO, @"Unknown path extension %@ for file %@", pathExtension, self.file);
    }
}

//把所有的配置文件解析后存放在self.table中
- (void)loadFromPlist {
    NSString *filepath = [[NSBundle mainBundle] pathForResource:self.file.stringByDeletingPathExtension ofType:self.file.pathExtension];
    NSDictionary *infos = [NSDictionary dictionaryWithContentsOfFile:filepath];
    NSSet *configThemes = [NSSet setWithArray:DKNightVersion.themes];
    for (NSString *key in infos) {
        NSMutableDictionary *themeToColorDictionary = [infos[key] mutableCopy];
        NSSet *themesInFile = [NSSet setWithArray:themeToColorDictionary.allKeys];
        NSAssert([themesInFile isEqualToSet:configThemes], @"Invalid theme to themes to color dictionary %@ for key %@", themeToColorDictionary, key);
        [themeToColorDictionary enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, NSString * _Nonnull obj, BOOL * _Nonnull stop) {
            UIColor *color = [self colorFromString:obj];
            themeToColorDictionary[key] = color;
        }];
        [self.table setValue:themeToColorDictionary forKey:key];
    }
}
image.png

1.3 DKColorTable的pickerWithKey方法会根据对应的属性,选择当前主题下的颜色

//注意DKColorPicker是一个block
- (DKColorPicker)pickerWithKey:(NSString *)key {
    NSParameterAssert(key);

    //key='TINT'
    //themeToColorDictionary 里 包含了不同主题下setTintColor可以设置的颜色
    //themeVersion为当前要设定的主题
    NSDictionary *themeToColorDictionary = [self.table valueForKey:key];
    DKColorPicker picker = ^(DKThemeVersion *themeVersion) {
        return [themeToColorDictionary valueForKey:themeVersion];
    };

    return picker;

}

1.4 在setTintColor同时还会保留要改变属性的sel和DKColorPicker,用于之后如果再次改变主题的时候,切换其他模式

#import "UIBarButtonItem+Night.h"
#import "DKNightVersionManager.h"
#import <objc/runtime.h>

@interface UIBarButtonItem ()

@property (nonatomic, strong) NSMutableDictionary<NSString *, DKColorPicker> *pickers;//每个空间都有一个pickers属性

@end

@implementation UIBarButtonItem (Night)


- (DKColorPicker)dk_tintColorPicker {
    return objc_getAssociatedObject(self, @selector(dk_tintColorPicker));
}

- (void)dk_setTintColorPicker:(DKColorPicker)picker {
    objc_setAssociatedObject(self, @selector(dk_tintColorPicker), picker, OBJC_ASSOCIATION_COPY_NONATOMIC);
    self.tintColor = picker(self.dk_manager.themeVersion);
    [self.pickers setValue:[picker copy] forKey:@"setTintColor:"];把block和sel放入self.pickers,因为picker是一个block,下次就可以直接根据主题切换颜色了
}

2. 如何切换主题

image.png

2.1 记录了新的主题key(用户下次启动app时调用)同时发送通知,让所有接到通知的控件改变颜色

- (void)setThemeVersion:(DKThemeVersion *)themeVersion {
    if ([_themeVersion isEqualToString:themeVersion]) {
        // if type does not change, don't execute code below to enhance performance.
        return;
    }
    _themeVersion = themeVersion;

    // Save current theme version to user default
    [[NSUserDefaults standardUserDefaults] setValue:themeVersion forKey:DKNightVersionCurrentThemeVersionKey];
    [[NSNotificationCenter defaultCenter] postNotificationName:DKNightVersionThemeChangingNotificaiton
                                                        object:nil];

    if (self.shouldChangeStatusBar) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
        if ([themeVersion isEqualToString:DKThemeVersionNight]) {
            [[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent];
        } else {
            [[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleDefault];
        }
#pragma clang diagnostic pop
    }
}

2.2 改变所有注册了通知的控件颜色

- (NSMutableDictionary<NSString *, DKColorPicker> *)pickers {
    NSMutableDictionary<NSString *, DKColorPicker> *pickers = objc_getAssociatedObject(self, @selector(pickers));
    if (!pickers) {
        
        @autoreleasepool {
            // Need to removeObserver in dealloc
            
            //在dealloc里来移除通知
            if (objc_getAssociatedObject(self, &DKViewDeallocHelperKey) == nil) {
                //__unsafe_unretained 这里使用这个属性是为了避免如果使用weak导致对象nil之后无法remove,而__unsafe_unretained只会让对象变成野指针,不影响remove                
                __unsafe_unretained typeof(self) weakSelf = self; // NOTE: need to be __unsafe_unretained because __weak var will be reset to nil in dealloc
                id deallocHelper = [self addDeallocBlock:^{
                    
                    [[NSNotificationCenter defaultCenter] removeObserver:weakSelf];
                }];
                objc_setAssociatedObject(self, &DKViewDeallocHelperKey, deallocHelper, OBJC_ASSOCIATION_ASSIGN);
            }
        }

        pickers = [[NSMutableDictionary alloc] init];
        objc_setAssociatedObject(self, @selector(pickers), pickers, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
        
        [[NSNotificationCenter defaultCenter] removeObserver:self name:DKNightVersionThemeChangingNotificaiton object:nil];

        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(night_updateColor) name:DKNightVersionThemeChangingNotificaiton object:nil];
    }
    return pickers;
}

//通过self.pickers 遍历,找到里面的picker然后通过主题改变颜色
- (void)night_updateColor {
    [self.pickers enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull selector, DKColorPicker  _Nonnull picker, BOOL * _Nonnull stop) {
        SEL sel = NSSelectorFromString(selector);
        id result = picker(self.dk_manager.themeVersion);
        [UIView animateWithDuration:DKNightVersionAnimationDuration
                         animations:^{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
                             [self performSelector:sel withObject:result];
#pragma clang diagnostic pop
                         }];
    }];
}

3. 如何切换图片

切换图片与切换颜色类似,可以参考上面的逻辑

image.png

3.1 DKImage的DKImagePickerWithNames方法会返回一个DKImagePicker(block)

DKImagePicker DKImagePickerWithNames(NSString *normalName, ...) {
    
    NSArray<DKThemeVersion *> *themes = [DKColorTable sharedColorTable].themes;
    NSMutableArray<NSString *> *names = [[NSMutableArray alloc] initWithCapacity:themes.count];
    [names addObject:normalName];
    NSUInteger num_args = themes.count - 1;
    va_list names_list;//VA_LIST 是在C语言中解决变参问题的一组宏,用于获取不确定个数的参数
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wvarargs"
    va_start(names_list, num_args);// 这里把上面得到的字符指针跳过num_args的内存地址
#pragma clang diagnostic pop
    for (NSUInteger i = 0; i < num_args; i++) {
        NSString *name = va_arg(names_list, NSString *);//会从第二个可变参数开始读取数据
        [names addObject:name];
    }
    va_end(names_list);//结束

    return [DKImage pickerWithNames:names];
}

具体可变参数的资料,可以参考这篇文章:全面解析C语言中可变参数列表

3.2 DKImagePicker会根据当前的主题,选择返回的img

//返回一个DKImagePicker,DKImagePicker里面会根据当前的主题,选择names里对应的img
+ (DKImagePicker)pickerWithNames:(NSArray<NSString *> *)names {
    DKColorTable *colorTable = [DKColorTable sharedColorTable];
    NSParameterAssert(names.count == colorTable.themes.count);
    return ^(DKThemeVersion *themeVersion) {
        NSUInteger index = [colorTable.themes indexOfObject:themeVersion];
        if (index >= colorTable.themes.count) {
            return [UIImage imageNamed:names[[colorTable.themes indexOfObject:DKThemeVersionNormal]]];
        }
        return [UIImage imageNamed:names[index]];
    };
}

3.3 对应的imageView会有一个分类,当set分类里的方法的时候,会根据传过来的DKImagePicker返回的img,进行赋值,并把block和sel存入self.pickers字典

- (void)dk_setImagePicker:(DKImagePicker)picker {
    objc_setAssociatedObject(self, @selector(dk_imagePicker), picker, OBJC_ASSOCIATION_COPY_NONATOMIC);
    self.image = picker(self.dk_manager.themeVersion);
    [self.pickers setValue:[picker copy] forKey:@"setImage:"];

}

总结

DKNightVersion是一个相对成熟的切换主题的第三方库了,因为公司的需要,也是花了一点时间阅读了他的源码,感觉开阔了一些自己的思路,值得注意的是,作者是我们国人,他的博客地址:博客地址真的很优秀的开发者,值得我们所有人学习!

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

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 11,611评论 4 59
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,563评论 25 707
  • 之前用过一次,忘了怎么用的,今天又看了一下。百度了一下,还是搜到了唐巧的博客,参考了一下步骤。博文链接 下载...
    alvin_ding阅读 657评论 0 0
  • 几十年的光阴 一道深深的壕沟 刻在你的脸上 也刻在我的心上 我拼尽力气想挽回一些什么 不敢奢求 一点温纯的记忆而已...
    浪迹天涯之歌阅读 115评论 0 1
  • 在一年半以前,沃尔沃曾经向外界公布了一部概念性视频,内容讲述的是车主可以将自己的沃尔沃汽车作为「快递中转站」,快递...
    一不小心学环境阅读 207评论 0 0