提供资讯、信息类的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这个鬼东西!参考://www.greatytc.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文件进行色值配置