DKNightVersion源碼閱讀筆記

DKNightVersion是git上一款優(yōu)秀的切換主題的第三方庫,如圖所示,你可以自定義主題,自由的切換文字&背景&圖片的顏色與內(nèi)容,以下是我的學(xué)習(xí)筆記.

image.png

地址:DKNightVersionDemo

結(jié)構(gòu)

image.png

流程

1. 如何設(shè)置默認(rèn)主題

image.png

以控件UIBarButtonItem為例

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

1.1 創(chuàng)建一個(gè)DKNightVersion,然后設(shè)定有哪些基本要切換的主題

//初始化DKNightVersion,把它變成一個(gè)單例,給定基本主題
+ (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會(huì)解析resource,得到對應(yīng)主題的對應(yīng)顏色等信息

//通過宏定義調(diào)取方法
#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方法會(huì)根據(jù)對應(yīng)的屬性,選擇當(dāng)前主題下的顏色

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

    //key='TINT'
    //themeToColorDictionary 里 包含了不同主題下setTintColor可以設(shè)置的顏色
    //themeVersion為當(dāng)前要設(shè)定的主題
    NSDictionary *themeToColorDictionary = [self.table valueForKey:key];
    DKColorPicker picker = ^(DKThemeVersion *themeVersion) {
        return [themeToColorDictionary valueForKey:themeVersion];
    };

    return picker;

}

1.4 在setTintColor同時(shí)還會(huì)保留要改變屬性的sel和DKColorPicker,用于之后如果再次改變主題的時(shí)候,切換其他模式

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

@interface UIBarButtonItem ()

@property (nonatomic, strong) NSMutableDictionary<NSString *, DKColorPicker> *pickers;//每個(gè)空間都有一個(gè)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,因?yàn)閜icker是一個(gè)block,下次就可以直接根據(jù)主題切換顏色了
}

2. 如何切換主題

image.png

2.1 記錄了新的主題key(用戶下次啟動(dòng)app時(shí)調(diào)用)同時(shí)發(fā)送通知,讓所有接到通知的控件改變顏色

- (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 這里使用這個(gè)屬性是為了避免如果使用weak導(dǎo)致對象nil之后無法remove,而__unsafe_unretained只會(huì)讓對象變成野指針,不影響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方法會(huì)返回一個(gè)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語言中解決變參問題的一組宏,用于獲取不確定個(gè)數(shù)的參數(shù)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wvarargs"
    va_start(names_list, num_args);// 這里把上面得到的字符指針跳過num_args的內(nèi)存地址
#pragma clang diagnostic pop
    for (NSUInteger i = 0; i < num_args; i++) {
        NSString *name = va_arg(names_list, NSString *);//會(huì)從第二個(gè)可變參數(shù)開始讀取數(shù)據(jù)
        [names addObject:name];
    }
    va_end(names_list);//結(jié)束

    return [DKImage pickerWithNames:names];
}

具體可變參數(shù)的資料,可以參考這篇文章:全面解析C語言中可變參數(shù)列表

3.2 DKImagePicker會(huì)根據(jù)當(dāng)前的主題,選擇返回的img

//返回一個(gè)DKImagePicker,DKImagePicker里面會(huì)根據(jù)當(dāng)前的主題,選擇names里對應(yīng)的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 對應(yīng)的imageView會(huì)有一個(gè)分類,當(dāng)set分類里的方法的時(shí)候,會(huì)根據(jù)傳過來的DKImagePicker返回的img,進(jìn)行賦值,并把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:"];

}

總結(jié)

DKNightVersion是一個(gè)相對成熟的切換主題的第三方庫了,因?yàn)楣镜男枰?也是花了一點(diǎn)時(shí)間閱讀了他的源碼,感覺開闊了一些自己的思路,值得注意的是,作者是我們國人,他的博客地址:博客地址真的很優(yōu)秀的開發(fā)者,值得我們所有人學(xué)習(xí)!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,229評論 4 61
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,237評論 25 708
  • 之前用過一次,忘了怎么用的,今天又看了一下。百度了一下,還是搜到了唐巧的博客,參考了一下步驟。博文鏈接 下載...
    alvin_ding閱讀 738評論 0 0
  • 幾十年的光陰 一道深深的壕溝 刻在你的臉上 也刻在我的心上 我拼盡力氣想挽回一些什么 不敢奢求 一點(diǎn)溫純的記憶而已...
    浪跡天涯之歌閱讀 129評論 0 1
  • 在一年半以前,沃爾沃曾經(jīng)向外界公布了一部概念性視頻,內(nèi)容講述的是車主可以將自己的沃爾沃汽車作為「快遞中轉(zhuǎn)站」,快遞...
    一不小心學(xué)環(huán)境閱讀 238評論 0 0