提供資訊、信息類的App一般都有白天和黑夜兩種閱讀模式
- 現有項目中皮膚切換思路
1、資源文件(圖片、plist色板值、接口讀取數據拼接html模版所使用到的樣式表CSS等),都需準備兩套。
2、對用到的控件進行父類繼承,擴展屬性用字符串設置圖片、文本顏色名稱,如UIButton包括:未選中圖片、高亮中圖片、選中圖片、禁用圖片;未選中文字顏色、高亮中文字顏色、選中文字顏色、禁用文字顏色;未選中背景圖片、高亮背景圖片、選中背景圖片、禁用背景圖片等。
3、皮膚切換,保存當前模式到本地:通過發送通知的方式,控件接收通知,通過工具類方法刷新重新讀取該皮膚模式下對應的顏色或圖片;web頁面讀取該皮膚模式下的樣式表,通過JS替換CSS的href。 -
DKNightVersion皮膚切換學習
1、目錄結構:
Core:核心類(DKColor顏色設置,DKImage圖片設置,DKColorTable處理皮膚配置文件,DKNightVersionManager皮膚管理類,NSObject+Night擴展一個DKNightVersionManager)
DeallocBlockExecutor:內存回收(移除通知)相關的回調
CoreAnimation:動畫Layer的擴展
Resources:皮膚配置文件
UIKit:皮膚控件的擴展
2、思路
2.1、擴展NSObject通過DKNightVersionManager單例來管理皮膚的切換,設置themeVersion后保存到本地,并通知其它視圖更新顏色。
- (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、顏色設置:如TableViewCell的背景顏色通過一個屬性dk_cellTintColorPicker
進行,實質是一個 block,它接收參數 DKThemeVersion *themeVersion
,但是會返回一個 UIColor *
;
UIKit擴展中.m文件中的屬性pickers
和NSObject+Night
中pickers
是一個東西。
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
TableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"];
// 項目中這里是寫的一個宏自動生成,效果跟寫一個UITableViewCell+Night類別是一樣的
cell.dk_cellTintColorPicker = DKColorPickerWithRGB(0xffffff, 0x343434, 0xfafafa);
return cell;
}
2.3、然后通過屬性關聯設置UITableView的 tintColor
;同時,每一個對象還持有一個pickers
數組,來存儲自己的全部 DKColorPicker:
。
@interface UITableViewCell ()
@property (nonatomic, strong) NSMutableDictionary<NSString *, DKColorPicker> *pickers;
@end
@implementation UITableViewCell (Night)
- (DKColorPicker)dk_cellTintColorPicker {
return objc_getAssociatedObject(self, @selector(dk_cellTintColorPicker));
}
- (void)dk_setCellTintColorPicker:(DKColorPicker)picker {
objc_setAssociatedObject(self, @selector(dk_cellTintColorPicker), picker, OBJC_ASSOCIATION_COPY_NONATOMIC);
self.tintColor = picker(self.dk_manager.themeVersion);
[self.pickers setValue:[picker copy] forKey:@"setTintColor:"];
}
@end
2.4、在第一次使用這個屬性時,當前對象注冊為 DKNightVersionThemeChangingNotificaiton
通知的觀察者。 pickers
屬性只有在對象的某個 DKColorPicker/DKImagePicker
首次被賦值時才會被創建。
在每次收到通知時,都會調用 night_update
方法,將當前主題傳入 DKColorPicker
,并再次執行,并將結果傳入對應的屬性 [self performSelector:sel withObject:result]
。
- (NSMutableDictionary<NSString *, DKColorPicker> *)pickers {
// 第一次進來pickers為空進入if
NSMutableDictionary<NSString *, DKColorPicker> *pickers = objc_getAssociatedObject(self, @selector(pickers));
if (!pickers) {
@autoreleasepool {
// Need to removeObserver in dealloc
if (objc_getAssociatedObject(self, &DKViewDeallocHelperKey) == nil) {
__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];
// 將局部變量pickers和當前對象的pickers關聯
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;
}
- (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
}];
}];
}
- objc_AssociationPolicy幾種類型區別:Objective-C中的Associated Objects - 曾靜的技術博客
1、OBJC_ASSOCIATION_ASSIGN
,給關聯對象指定弱引用,相當于@property(assign)
或@property(unsafe_unretained)
。
2、OBJC_ASSOCIATION_RETAIN_NONATOMIC
,給關聯對象指定非原子的強引用,相當于@property(nonatomic,strong)
或@property(nonatomic,retain)
。
3、OBJC_ASSOCIATION_COPY_NONATOMIC
, 給關聯對象指定非原子的copy特性,相當于@property(nonatomic,copy)
。
4、OBJC_ASSOCIATION_RETAIN
,給關聯對象指定原子強引用,相當于@property(atomic,strong)
或@property(atomic,retain)
。
5、OBJC_ASSOCIATION_COPY
,給關聯對象指定原子copy特性,相當于@property(atomic,copy)
。
objc_setAssociatedObject:用來把一個對象與另一個對象進行關聯。一共需要四個參數,分別是:源對象,關鍵字,關聯的對象和一個關聯策略。源對象和關聯對象就是需要進行關聯的兩個對象; 關鍵字
是一個void類型的指針, 每一個關聯的關鍵字必須是唯一的,通常都是會采用靜態變量來作為關鍵字
,一般情況下也可以取@selector(function_name)即取得一個function的id作為關鍵字;關聯策略是一個枚舉,用來表示兩個對象的關聯程度。
objc_getAssociatedObject:和objc_setAssociatedObject配套使用,它是獲取相關聯的對象時使用的,objc_getAssociatedObject:兩個參數,源對象、關鍵字(注意關鍵字唯一且一致)。
- run Time在項目中的運用
實質:是一個運行時庫(Runtime Library),它是一個主要使用 C 和匯編寫的庫,為 C 添加了面相對象的能力并創造了 Objective-C。這就是說它在類信息(Class information) 中被加載,完成所有的方法分發,方法轉發,等等。Objective-C runtime 創建了所有需要的結構體。Objective-C為什么有面相對象的能力?就是因為有runtime這個鬼東西!參考:http://www.lxweimin.com/p/bba1ac264873
1、動態添加屬性;
2、方法切換
云信消息處理:message的時間與處理(13位處理成10位)
+(void)load{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 要特別注意你替換的方法到底是個性質的方法
// When swizzling a Instance method, use the following:
// Class class = [self class];
// When swizzling a class method, use the following:
Class class = object_getClass((id)self);
SEL originalSelector = @selector(systemMethod_PrintLog);
SEL swizzledSelector = @selector(ll_imageName);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
BOOL didAddMethod = class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
3、獲取一個類的所有成員變量
獲得某個類的所有成員變量 Ivar *class_copyIvarList(Class cls , unsigned int *outCount)
:(哪個類,放一個接收值的地址,用來存放屬性的個數),返回值:存放所有獲取到的屬性
獲得成員變量的名字 const char *ivar_getName(Ivar v)
獲得成員變量的類型 const char *ivar_getTypeEndcoding(Ivar v)
unsigned int outCount = 0;
Ivar *ivars = class_copyIvarList([Person class], &outCount);
// 遍歷所有成員變量
for (int i = 0; i < outCount; i++) {
// 取出i位置對應的成員變量
Ivar ivar = ivars[i];
const char *name = ivar_getName(ivar);
const char *type = ivar_getTypeEncoding(ivar);
NSLog(@"成員變量名:%s 成員變量類型:%s",name,type);
}
// 注意釋放內存!
free(ivars);
Objective C類方法load和initialize的區別
1、load是只要類所在文件被引用就會被調用,而initialize是在類或者其子類的第一個方法被調用前調用。所以如果類沒有被引用進項目,就不會有load調用;但即使類文件被引用進來,但是沒有使用,那么initialize也不會被調用。
2、相同點在于:方法只會被調用一次。runtime 完整總結
來自南峰子博客,就是對“objc/runtime.h”的解讀查看Demo請點擊
使用plist文件進行色值配置