iOS 主题换肤杂谈

陆续看过几种 iOS 主题换肤的代码,现简单记录下思路。

一般需要换肤的视图可能有 UILabelUISwitchUIButton,大多都是继承自 UIView,当然还有一些不是继承自 UIView 的,比如 UIBarButtonItem
有的需要改变字体和背景的颜色,有的需要更换不同的按钮图片。不同视图设置主题的方式不尽相同,但都不可避免,还是得一种一种地找出来调用。

这里讨论的是如何在更换主题的时候,告诉那些现存的需要换主题的视图去更改。大致的思路都比较相近,就是给每个视图绑定一个键值,主题切换的时候改变键值所对应的颜色或图片,然后重新调用 setTextColor 等方法。

Root window

DKNightVersion 这个库只支持日夜间切换,早期的版本是直接给 view 绑定两种颜色,主题切换的时候就直接递归遍历 root key windon 的所有子视图,然后通过 swizzle 设置另外一种颜色。这种做法简单粗暴,但并不是每个视图都需要去遍历。

而现在这个库改进了很多。
在设置颜色的时候是间接给 view 绑定一个返回颜色的 block,顺带注册通知,主题切换时发送通知即可。

但这个库有一个问题(已经提pr修复了),视图被释放的时候并没有调用 removeObserver 来移除通知。
那要怎么移除通知呢?category 里面是不能重写 dealloc 方法的,具体我们后面看看。

NSNotification

ThemeManager 这个来源于腾讯的某个讲座,只是个小 demo,思路基本上介绍清楚了。

- (void)setThemeMap:(NSDictionary *)themeMap
{
objc_setAssociatedObject(self, &kUIView_ThemeMap, themeMap, OBJC_ASSOCIATION_COPY_NONATOMIC);

if (themeMap) {
@autoreleasepool {
// Need to removeObserver in dealloc
if (objc_getAssociatedObject(self, &kUIView_DeallocHelper) == 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:^{
NSLog(@"deallocing %@", weakSelf);
[[NSNotificationCenter defaultCenter] removeObserver:weakSelf];
}];
objc_setAssociatedObject(self, &kUIView_DeallocHelper, deallocHelper, OBJC_ASSOCIATION_ASSIGN);
}

[[NSNotificationCenter defaultCenter] removeObserver:self name:kThemeDidChangeNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(themeChanged) name:kThemeDidChangeNotification object:nil];
[self themeChanged];
}
} else {
[[NSNotificationCenter defaultCenter] removeObserver:self name:kThemeDidChangeNotification object:nil];
}
}

它也是用了通知,而巧妙的地方在于移除通知的方法。

addDeallocBlock: 会返回另外一个自定义对象,并保存这个 block,视图被释放之后,它持有的这个自定义对象也被释放,那么就在它的 dealloc 里面调用 block,从而移除通知。

NSHashTable

NSHashTable 类似于 NSSet,但和 NSSet 有个很大的不同,就是可以持有weak类型的成员变量。

NSHashTable *hashTable = [NSHashTable weakObjectsHashTable];

那么我们就可以把它用来替换上文的 NSNotificationCenter,把需要换肤的视图注册到 NSHashTable。因为是弱引用,不用担心它们被强引用而无法释放,而且也不需要手动注销。