這篇文章主要為大家展示了“如何使用iOS實(shí)現(xiàn)換膚功能的簡(jiǎn)單處理框架”,內(nèi)容簡(jiǎn)而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領(lǐng)大家一起研究并學(xué)習(xí)一下“如何使用iOS實(shí)現(xiàn)換膚功能的簡(jiǎn)單處理框架”這篇文章吧。
成都創(chuàng)新互聯(lián)致力于成都網(wǎng)站設(shè)計(jì)、網(wǎng)站建設(shè),成都網(wǎng)站設(shè)計(jì),集團(tuán)網(wǎng)站建設(shè)等服務(wù)標(biāo)準(zhǔn)化,推過標(biāo)準(zhǔn)化降低中小企業(yè)的建站的成本,并持續(xù)提升建站的定制化服務(wù)水平進(jìn)行質(zhì)量交付,讓企業(yè)網(wǎng)站從市場(chǎng)競(jìng)爭(zhēng)中脫穎而出。 選擇成都創(chuàng)新互聯(lián),就選擇了安全、穩(wěn)定、美觀的網(wǎng)站建設(shè)服務(wù)!
前言
換膚功能是在APP開發(fā)過程中遇到的比較多的場(chǎng)景,為了提供更好的用戶體驗(yàn),許多APP會(huì)為用戶提供切換主題的功能。主題顏色管理涉及到的的步驟有
顏色配置使用顏色UI元素動(dòng)態(tài)變更的能力動(dòng)態(tài)修改配置主題包管理如何實(shí)施優(yōu)化
顏色配置
因?yàn)樯婕暗蕉喾N配置,所以以代碼的方式定義顏色實(shí)踐和維護(hù)的難度是比較高的,一種合適的方案是--顏色的配置是通過配置文件的形式進(jìn)行導(dǎo)入的。配置文件會(huì)經(jīng)過轉(zhuǎn)換步驟,最終形成代碼層級(jí)的配置,以全局的方式提供給各個(gè)模塊使用,這里會(huì)涉及到一個(gè)顏色管理者的概念,一般地這回事一個(gè)單例對(duì)象,提供全局訪問的接口。同一個(gè)APP中在不同的模塊中保存不同的主題顏色配置,在不同的層級(jí)中也可以存在不同的主題顏色配置,因?yàn)樯婕暗綄蛹?jí)間的配置差異,所以顏色的配置需要引入一個(gè)等級(jí)的概念,一般地較高層級(jí)顏色的配置等級(jí)是高于較低層級(jí)的,存在相同的配置較高層級(jí)的配置會(huì)覆蓋較低層級(jí)的配置。
我們采用的顏色配置的文件形如下面所示,為什么是在一個(gè)json文件的colorkey下面呢,是為了考慮到未來的擴(kuò)展性,如果不同的主題會(huì)涉及到一些尺寸值的差異化,我們可以添加dimensionskey進(jìn)行擴(kuò)展配置。
{ "color": { "Black_A":"323232", "Black_AT":"323232", "Black_B":"888888", "Black_BT":"888888", "White_A":"ffffff", "White_AT":"ffffff", "White_AN":"ffffff", "Red_A":"ff87a0", "Red_AT":"ff87a0", "Red_B":"ff5073", "Red_BT":"ff5073", "Colour_A":"377ce4", "Colour_B":"6aaafa", "Colour_C":"ff8c55", "Colour_D":"ffa200", "Colour_E":"c4a27a", }}
有了以上的配置,顏色配置的工作主要就是解析該配置文件,把配置保存在一個(gè)單例對(duì)象中即可,這部分主要的步驟如下:
配置文件類表根據(jù)等級(jí)排序獲取每個(gè)配置文件中的配置,進(jìn)行保存通知外部主題顏色配置發(fā)生改變
對(duì)應(yīng)的代碼如下,這里有個(gè)需要注意的地方是,加載配置文件的時(shí)候使用了文件讀寫鎖進(jìn)行讀寫的鎖定操作,防止讀臟數(shù)據(jù)的發(fā)生,直到配置文件加載完成,釋放讀寫鎖,這時(shí)讀進(jìn)程可以繼續(xù)。
- (void)loadConfigWithFileName:(NSString *)fileName level:(NSInteger)level { if (fileName.length == 0) { return; } pthread_rwlock_wrlock(&_rwlock); __block BOOL finded = NO; [self.configFileQueue enumerateObjectsUsingBlock:^(YTThemeConfigFile *_Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) { if ([obj.fileName isEqualToString:fileName]) { finded = YES; *stop = YES; } }]; if (!finded) { // 新增配置文件 YTThemeConfigFile *file = [[YTThemeConfigFile alloc] init]; file.fileName = fileName; file.level = level; [self.configFileQueue addObject:file]; // 優(yōu)先級(jí)排序 [self.configFileQueue sortUsingComparator:^NSComparisonResult(YTThemeConfigFile *_Nonnull obj1, YTThemeConfigFile *_Nonnull obj2) { if (obj1.level > obj2.level) { return NSOrderedDescending; } return NSOrderedAscending; }]; [self setupConfigFilesContainDefault:YES]; } pthread_rwlock_unlock(&_rwlock);}- (void)setupConfigFilesContainDefault:(BOOL)containDefault { NSMutableDictionary *defaultColorDict = nil, *currentColorDict = nil; // 加載默認(rèn)配置 if (containDefault) { defaultColorDict = [NSMutableDictionary dictionary]; [self loadConfigDataWithColorMap:defaultColorDict valueMap:nil isDefault:YES]; self.defaultColorMap = defaultColorDict; } // 加載主題配置 if (_themePath.length > 0) { currentColorDict = [NSMutableDictionary dictionary]; [self loadConfigDataWithColorMap:currentColorDict valueMap:nil isDefault:NO]; self.currentColorMap = currentColorDict; } // 發(fā)送主體顏色變更通知 [self notifyThemeDidChange];}- (void)notifyThemeDidChange { NSArray *allActionObjects = self.actionMap.objectEnumerator.allObjects; for (YTThemeAction *action in allActionObjects) { [action notifyThemeDidChange]; }}- (void)loadConfigDataWithColorMap:(NSMutableDictionary *)colorMap valueMap:(NSMutableDictionary *)valueMap isDefault:(BOOL)isDefault { // 每一次新增一個(gè)配置文件,所有配置文件都得重新計(jì)算一次,這里有很多重復(fù)多余的工作 [self.configFileQueue enumerateObjectsUsingBlock:^(YTThemeConfigFile *_Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) { NSDictionary *dict = nil; if (isDefault) { dict = obj.defaultDict; } else { dict = obj.currentDict; } if (dict.count > 0) { [self loadThemeColorTo:colorMap from:dict]; // 將所有配置表中的color字段的數(shù)據(jù)都放到colorMap中 } }];}- (void)loadThemeColorTo:(NSMutableDictionary *)dictionary from:(NSDictionary *)from { NSDictionary
管理者處理處理配置之外,還需要暴露外部接口給客戶端使用,以用于獲取不同主題下對(duì)應(yīng)的顏色色值、圖片資源、尺寸信息等和主題相關(guān)的信息。比如我們會(huì)提供一個(gè)colorForKey方法獲取不同主題下的同一個(gè)key對(duì)應(yīng)的顏色色值,獲取色值的大致步驟如下:
從當(dāng)前的主題配置中獲取從默認(rèn)的主題配置中獲取從預(yù)留的主題配置中獲取如果重定向的配置,遞歸處理以上步驟都完成還未找到返回默認(rèn)黑色
這里使用了讀寫鎖的寫鎖,如果同時(shí)有寫操作獲取了該鎖,讀取進(jìn)程會(huì)阻塞直到寫操作的完成釋放鎖。
/** 獲取顏色值 */- (UIColor *)colorForKey:(NSString *)key { pthread_rwlock_rdlock(&_rwlock); UIColor *color = [self colorForKey:key isReserveKey:NO redirectCount:0]; pthread_rwlock_unlock(&_rwlock); return color;}- (UIColor *)colorForKey:(NSString *)key isReserveKey:(BOOL)isReserveKey redirectCount:(NSInteger)redirectCount { if (key == nil) { return nil; } ///正常獲取色值 id colorObj = [_currentColorMap objectForKey:key]; if (colorObj == nil) { colorObj = [_defaultColorMap objectForKey:key]; } if (isReserveKey && colorObj == nil) { return nil; } ///看看是否有替補(bǔ)key if (colorObj == nil) { NSString *reserveKey = [_reserveKeyMap objectForKey:key]; if (reserveKey) { colorObj = [self colorForKey:reserveKey isReserveKey:YES redirectCount:redirectCount]; } } ///查看當(dāng)前key 能否轉(zhuǎn)成 color if (colorObj == nil) { colorObj = [UIColor yt_colorWithHexString:key]; } if ([colorObj isKindOfClass:[UIColor class]]) { ///如果是 重定向 或者 替補(bǔ) key 的color 要設(shè)置到 當(dāng)前 colorDict 里面 // 重定向的配置形如:"Red_A":"Red_B", if (redirectCount > 0 || isReserveKey) { [_currentColorMap ?: _defaultColorMap setObject:colorObj forKey:key]; } return colorObj; } else { if (redirectCount < 3) { // 重定向遞歸 return [self colorForKey:colorObj isReserveKey:NO redirectCount:redirectCount + 1]; } else { return [UIColor blackColor]; } }}
使用顏色
顏色的使用也是經(jīng)由管理者的,為了方便,定義一個(gè)顏色宏提供給客戶端使用
#define YTThemeColor(key) ([[YTThemeManager sharedInstance] colorForKey:key])
客戶端使用的代碼如下:
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0, 20, 200, 40)];label.text = @"Text";label.textColor = YTThemeColor(kCK_Red_A);label.backgroundColor = YTThemeColor(kCK_Black_H);[self.view addSubview:label];
另外,因?yàn)轭伾渲玫膋ey為字符串類型,直接使用字符串常量并不是個(gè)好辦法,所以把對(duì)應(yīng)的字符串轉(zhuǎn)換為宏定義是一個(gè)相對(duì)好的辦法。第一個(gè)是方便使用,可以使用代碼提示;第二個(gè)是不容易出錯(cuò),特別是長(zhǎng)的字符串;第三個(gè)也會(huì)一定程度上的提高效率。
YTColorDefine類的宏定義
// .h 中的聲明///BlackFOUNDATION_EXTERN NSString *kCK_Black_A;FOUNDATION_EXTERN NSString *kCK_Black_AT;FOUNDATION_EXTERN NSString *kCK_Black_B;FOUNDATION_EXTERN NSString *kCK_Black_BT;// .m 中的定義NSString *kCK_Black_A = @"Black_A";NSString *kCK_Black_AT = @"Black_AT";NSString *kCK_Black_B = @"Black_B";NSString *kCK_Black_BT = @"Black_BT";
主題包管理
在實(shí)際的落地項(xiàng)目中,主題包管理涉及到的事項(xiàng)包括主題包下載和解壓和動(dòng)態(tài)加載主題包等內(nèi)容,最后的一步是更換主題配置文件所在的配置路徑,為了演示的方便,我們會(huì)把不同主題的資源放置在bundle中某一個(gè)特定的文件夾下,通過切換管理者中的主題路徑配置來達(dá)到切換主題的效果,和動(dòng)態(tài)下載更換主題的步驟是一樣的。
管理者提供一個(gè)設(shè)置主題配置的配置路徑的方法,在該方法中改變配置路徑的同時(shí),重新加載配置即可,代碼如下
/** 設(shè)置主題文件的路徑 @param themePath 文件的路徑 */- (void)setupThemePath:(NSString *)themePath { pthread_rwlock_wrlock(&_rwlock); _themePath = [themePath copy]; self.currentColorMap = nil; if ([_themePath.lowercaseString isEqualToString:[[NSBundle mainBundle] resourcePath].lowercaseString]) { _themePath = nil; } self.currentThemePath = _themePath; for (int i = 0; i < self.configFileQueue.count; i++) { YTThemeConfigFile *obj = [self.configFileQueue objectAtIndex:i]; [obj resetCurrentDict]; } [self setupConfigFilesContainDefault:NO]; pthread_rwlock_unlock(&_rwlock);}
如何實(shí)施
以上的流程涉及到的只是iOS平臺(tái)下的一個(gè)技術(shù)解決方案,真實(shí)的實(shí)踐過程中會(huì)涉及到安卓平臺(tái)、Web頁面、UI出圖的標(biāo)注,這些是要進(jìn)行統(tǒng)一處理的,才能在各個(gè)端上有一致的體驗(yàn)。第一步就是制定合理的顏色規(guī)范,把規(guī)范同步給各個(gè)端的利益相關(guān)人員;第二部是UI出圖顏色是規(guī)范的顏色定義值,而不是比如#ffffff這樣的顏色,需要是比如White_A這樣規(guī)范的顏色定義值,這樣客戶端處理使用的就是White_A這個(gè)值,不用管在不同主題下不同的顏色表現(xiàn)形式。
優(yōu)化
loadConfigDataWithColorMap方法調(diào)用的優(yōu)化
如果模塊很多,每個(gè)模塊都會(huì)調(diào)用loadConfigWithFileName加載配置文件,那么loadConfigDataWithColorMap方法處理文件的時(shí)間復(fù)雜度是O(N*N),會(huì)重復(fù)處理很多多余的工作,理想的做法是底層保存一份公有的顏色配置,然后在APP層加載一份定制化的配置,在模塊中不用再加載主題配置文件,這樣會(huì)提高效率。
附:讀寫鎖pthread_rwlock_t的使用
讀寫鎖是用來解決讀者寫者問題的,讀操作可以共享,寫操作是排他的,讀可以有多個(gè)在讀,寫只有唯一個(gè)在寫,同時(shí)寫的時(shí)候不允許讀。
具有強(qiáng)讀者同步和強(qiáng)寫者同步兩種形式
強(qiáng)讀者同步:當(dāng)寫者沒有進(jìn)行寫操作,讀者就可以訪問;
強(qiáng)寫者同步:當(dāng)所有寫者都寫完之后,才能進(jìn)行讀操作,讀者需要最新的信息,一些事實(shí)性較高的系統(tǒng)可能會(huì)用到該所,比如定票之類的。
讀寫鎖的操作:
讀寫鎖的初始化:
定義讀寫鎖: pthread_rwlock_t m_rw_lock;
函數(shù)原型: pthread_rwlock_init(pthread_rwlock_t * ,pthread_rwattr_t *);
返回值:0,表示成功,非0為一錯(cuò)誤碼
讀寫鎖的銷毀:
函數(shù)原型: pthread_rwlock_destroy(pthread_rwlock_t* );
返回值:0,表示成功,非0表示錯(cuò)誤碼
獲取讀寫鎖的讀鎖操作:分為阻塞式獲取和非阻塞式獲取,如果讀寫鎖由一個(gè)寫者持有,則讀線程會(huì)阻塞直至寫入者釋放讀寫鎖。
阻塞式:
函數(shù)原型:pthread_rwlock_rdlock(pthread_rwlock_t*);
非阻塞式:
函數(shù)原型:pthread_rwlock_tryrdlock(pthread_rwlock_t*);
返回值: 0,表示成功,非0表示錯(cuò)誤碼,非阻塞會(huì)返回ebusy而不會(huì)讓線程等待
獲取讀寫鎖的寫鎖操作:分為阻塞和非阻塞,如果對(duì)應(yīng)的讀寫鎖被其它寫者持有,或者讀寫鎖被讀者持有,該線程都會(huì)阻塞等待。
阻塞式:
函數(shù)原型:pthread_rwlock_wrlock(pthread_rwlock_t*);
非阻塞式:
函數(shù)原型:pthread_rwlock_trywrlock(pthread_rwlock_t*);
返回值: 0,表示成功
釋放讀寫鎖:
函數(shù)原型:pthread_rwlock_unlock(pthread_rwlock_t*);
總結(jié)(轉(zhuǎn)):
互斥鎖與讀寫鎖的區(qū)別:
當(dāng)訪問臨界區(qū)資源時(shí)(訪問的含義包括所有的操作:讀和寫),需要上互斥鎖;
當(dāng)對(duì)數(shù)據(jù)(互斥鎖中的臨界區(qū)資源)進(jìn)行讀取時(shí),需要上讀取鎖,當(dāng)對(duì)數(shù)據(jù)進(jìn)行寫入時(shí),需要上寫入鎖。
讀寫鎖的優(yōu)點(diǎn):
對(duì)于讀數(shù)據(jù)比修改數(shù)據(jù)頻繁的應(yīng)用,用讀寫鎖代替互斥鎖可以提高效率。因?yàn)槭褂没コ怄i時(shí),即使是讀出數(shù)據(jù)(相當(dāng)于操作臨界區(qū)資源)都要上互斥鎖,而采用讀寫鎖,則可以在任一時(shí)刻允許多個(gè)讀出者存在,提高了更高的并發(fā)度,同時(shí)在某個(gè)寫入者修改數(shù)據(jù)期間保護(hù)該數(shù)據(jù),以免任何其它讀出者或?qū)懭胝叩母蓴_。
讀寫鎖描述:
獲取一個(gè)讀寫鎖用于讀稱為共享鎖,獲取一個(gè)讀寫鎖用于寫稱為獨(dú)占鎖,因此這種對(duì)于某個(gè)給定資源的共享訪問也稱為共享-獨(dú)占上鎖。
有關(guān)這種類型問題(多個(gè)讀出者和一個(gè)寫入者)的其它說法有讀出者與寫入者問題以及多讀出者-單寫入者鎖。
以上是“如何使用iOS實(shí)現(xiàn)換膚功能的簡(jiǎn)單處理框架”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對(duì)大家有所幫助,如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道!