换肤框架SakuraKit实践及源码解析

前言

最近在做换肤,但是找了很多方案发现对代码的侵入性都非常大,而且公司项目做了组件化,网上通用的方案虽然也能实现但是代价太大,而且不方便迭代更新。当换肤功能完成大半的时候突发发现一个流弊的换肤框架,SakuraKit我。。。
先附上原文作者的链接:http://08643.cn/p/8930b4496023
作者的 demo本人感觉过于复杂,我这里讲下从简单的使用到源码

使用步骤

  1. 编写json文件Skin_Tennis.json
{
   "PersonalCenter":{
        "skinFlagColor":"ffffff",
        "skinFlagName":"网球主题",
        "backImage":"back_black",
        "backTitleColor":"#999999",
        "headerBackgroundImage":"me_header_tennis",
        "buttonBackgroundImage":"button_background_tennis"
    }
}
  1. 调用
UIImageView *bgImageView = [[UIImageView alloc] init];
bgImageView.contentMode = UIViewContentModeScaleAspectFill;
bgImageView.sakura.image(@"PersonalCenter.headerBackgroundImage");
[self addSubview:bgImageView];

UIButton *button = [[UIButton alloc] init];
button.frame = CGRectMake(0, 0, 100, 44);
button.sakura.backgroundImage(@"PersonalCenter.buttonBackgroundImage", UIControlStateNormal);
button.sakura.titleColor(@"PersonalCenter. backTitleColor", UIControlStateNormal);
  1. 切换皮肤
[TXSakuraManager shiftSakuraWithName:@"Skin_Tennis" type:TXSakuraTypeMainBundle];
  1. 恭喜你完成了一个简单的本地换肤

源码?

我们这里以bgImageView.sakura.image(@"PersonalCenter.headerBackgroundImage");举例,我们点进. sakura看看

TXSakuraCategoryImplementation(UIImageView, TXSakuraImageView)

懵逼,懵逼就对了,作者怕是搞c++出身的,别怕其实就是个宏而已解析出来就是:

@interface UIImageView (TX)
@property (strong, nonatomic) TXSakuraImageView *sakura;
@end

extern void *kTXSakuraKey;

@implementation UIImageView(TX)

@dynamic sakura;
- (UIImageView *)sakura {
    TXSakuraImageView *obj = objc_getAssociatedObject(self, kTXSakuraKey);
    if (!obj) {
        obj = [TXSakuraImageView sakuraWithOwner:self]
        objc_setAssociatedObject(self, kTXSakuraKey, obj, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    return obj;
}
@end

一个分类而已,并且给当前UIImageView对象添加了一个sakura参数,并实现懒加载
. sakura知道了,那么来看看.image(@"PersonalCenter.headerBackgroundImage");可以跟参数,那肯定是个block,源码:

- (TXSakuraImageViewBlock)image {
    return (TXSakuraImageViewBlock)[super tx_sakuraImageBlockWithName:NSStringFromSelector(_cmd)];
}

别怕我们一层层来看,注意看注释,有的简单方法我就不展开了直接写结果了

- (TXSakuraBlock)tx_sakuraImageBlockWithName:(NSString *)name {
    
    // 实际name=@"image",path=@"PersonalCenter.headerBackgroundImage"
    return ^TXSakura *(NSString *path){
        return [self send1DMsgObjectWithName:name keyPath:path arg:kTXSakuraArgImage valueBlock:^NSObject *(NSString *keyPath) {
            // 这里是通过keyPath取出对应的image
            return [TXSakuraManager tx_imageWithPath:keyPath];
        }];
    };
}

先来看看send1DMsgObjectWithName方法

- (instancetype)send1DMsgObjectWithName:(NSString *)name
                                keyPath:(NSString *)keyPath
                                    arg:(NSString *)arg
                             valueBlock:(id(^)(NSString *))valueBlock {
    // 这里不过是做了一些拼接生成一个setImage:的sel,并且以sel名为key将keyPaht存到一个全局的字典里面innerSkins1D,最终得到的innerSkins1D是:
    /**
     {
         "setImage:" =     {
            "com.tingxins.sakura.arg.image" = "PersonalCenter.headerBackgroundImage";
         };
     }
     */
    SEL sel = [self prepareForSkin1DWithName:name keyPath:keyPath argKey:arg];
    
    if (!valueBlock) return [TXSakuraTrash sakuraWithOwner:self];
    NSObject *obj = valueBlock(keyPath);
    // 给imageView对象send一个sel的方法也就是调用setImage:,参数为valueBlock返回的值,也就是[TXSakuraManager tx_imageWithPath:keyPath];的返回值
    [self send1DMsgWithSEL:sel objValue:obj];
    return self;
}

看完了设置的方法,那么来看看看切换皮肤的方法(有注释的地方才是关键点)

[TXSakuraManager shiftSakuraWithName:@"Skin_Tennis" type:TXSakuraTypeMainBundle];
+ (BOOL)shiftSakuraWithName:(TXSakuraName *)name type:(TXSakuraType)type {
    if (name &&
        [name isEqualToString:_currentSakuraName]) return NO;
    if (!name) name = kTXSakuraDefault;
    switch (type) {
        case TXSakuraTypeMainBundle:
            _resourcesPath = nil;
            // 这里是通过文件名拿到对应的文件路径
            _configsFilePath = [self tx_getSakuraConfigsFileBundlePathWithName:name];
            break;
        case TXSakuraTypeSandbox:{
            _resourcesPath = [self tx_getSakuraResourceSandboxPathWithName:name];
            _configsFilePath = [self tx_tryGetSakuraConfigsFileSandboxPathWithName:name];
            if (!_configsFilePath.length && _resourcesPath.length) {
                _configsFilePath = [self tx_getSakuraConfigsFileBundlePathWithName:kTXSakuraDefault];
            }
        }
            break;
        default:
            break;
    }
    
    if (_configsFilePath.length) {
        // 这里仅仅把当前选择的皮肤名存到沙盒
        [self saveCurrentSakuraInfosWithName:name type:type];
        // 这里才是关键,通知!大部分换肤框架都免不了通知
        [[NSNotificationCenter defaultCenter] postNotificationName:TXSakuraSkinChangeNotification object:nil];
        return YES;
    }
#ifdef DEBUG
    else {
        NSLog(@"resources not exists!");
    }
    NSLog(@"%@", _configsFilePath);
#endif
    return NO;
}

看到通知是不是明白了大概了,对你猜得没错,之前创建的sakura对象里会接接收这个通知

- (instancetype)initWithOwner:(id)owner {
    if (self = [super init]) {
        _owner = owner;
        _imageRenderingMode = UIImageRenderingModeAlwaysOriginal;
        // TXSakura类里面接收通知
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateSakuraSkins) name:TXSakuraSkinChangeNotification object:nil];
    }
    return self;
}
- (void)updateSakuraSkins {
    // 一维参数,self.skins1D其实就是返回上面说的全局的innerSkins1D,还记得里面存的什么吗?
    /**
     {
        "setImage:" =     {
            "com.tingxins.sakura.arg.image" = "PersonalCenter.headerBackgroundImage";
        };
     }
     */
    [self updateSakuraWith1DSkins:self.skins1D];
    // 二维参数,不知道干嘛用,好像没用到,为了拓展?
    [self updateSakuraWith2DSkins:self.skins2D];
}

老铁们应该已经猜到接下来该干嘛了吧,updateSakuraWith1DSkins:这里就不展开了,里面就是拿到innerSkins1D这个全局的字典,根据PersonalCenter.headerBackgroundImage这个可以取出创建图片名,然后调用setImage:方法

下载?

待更新

最后感谢看完我BB这么多,希望能带给给需要做换肤的小伙伴有一些启发,demo待我稍作修改稍后附上,有更深入的研究后会更新此文

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

推荐阅读更多精彩内容