主题的切换就是可以增强用户体验、结合运营活动的一个点:譬如 QQ 的夜间模式,节日里电商 APP 的皮肤切换等等的这些小细节往往就是赢得用户尊重的根本。
让 APP 已有的控件能切换主题可以用子类化,swizzle 或 category 来实现,其中子类化和 category 实现起来差不多,都是让控件调特定的方法达到切换风格的效果,而 swizzle 的影响范围会比较广,使用的时候可以通过 Associated Object 添加一个标记值,让需要切换风格的控件设置这个标记值,让标记值来决定是否需要 swizzle。考虑到上述几种方案的复杂度,最后选择了 category 来实现。
主题管理类的核心功能就是负责主题的更新,切换。正如下图所示,想让主题管理类通知到这么多待切换的 category 并不是一件容易的事,因为觉得在 category 上添加观察者并不是太好的设计,你很难知道什么时机该把观察者移除了。
![](https://img.haomeiwen.com/i1904217/76f7885bf7ecb231.png)
这也就意味着,可能需要自己动手来实现回调机制了,让切换主题相关的 category 通过主题管理类注册一个回调 block,主题类维护使用一个字典维护这些 block,待切换时由主题管理类统一回调,达到类似 Notification 的效果。
UILabel的categroy
![](https://img.haomeiwen.com/i1904217/80ef1ef21847d13d.png)
![](https://img.haomeiwen.com/i1904217/3d713666a91fd1ee.png)
只是在方法的底部添加了注册 block 的方法,而注册 block 的方法也十分简单,只需依据 key 判断下是否需要将 block 加入代码中。
那么问题来了,到底该如何设计一个这样的 key 呢?
同一个控件的主题 category 有多个需要切换主题的方法(例如 UIButton 有setTitleColor:forState: 和 setImage:forState:);
多个控件都是通过同一个 categroy 来切换主题(例如有多个 UIButton 需要切换主题);
其实统筹来看,就是如何通过某个类的实例和所需定制主题的方法来确定一个 key。
一开始很自然的拼了一个类的地址和方法名来作为key[NSString stringWithFormat:@"%p#%@", class, NSStringFromSelector(selector)]
流程能跑起来了,但是问题也很明显,只知道一个对象的指针字符串,根据对象是否被释放而进行的字典清理将变得难以实现:
![](https://img.haomeiwen.com/i1904217/bf2bc7d1eddd7cbc.png)
那么,应该怎样设计 block 对应的 key 呢?
能从 key 中获取到注册的类;
key 中也存有方法做 key 的唯一性和对象访问该方法安全性的校验respondsToSelector;
为此,实现了一个辅助的 model,用以访问需要注册的对象实例和方法名,同时作为 Dictionary 的 key,它还需要实现 NSCoping 协议:
![](https://img.haomeiwen.com/i1904217/6217b33be415fbfc.png)
weak 修饰的对象实例能够在对象被释放后自动置 nil,下面附上最初的.m文件实现。
![](https://img.haomeiwen.com/i1904217/42be40bd5602dd87.png)
效果图
![](https://img.haomeiwen.com/i1904217/4c014ad30a84185a.gif)
总结
本文描述了实现一个主题管理类的大致思路,希望能对读者有所帮助。后来笔者想到既然有了 target 和 selector,能不能通过 NSInvocation 来动态调用,就不借助 block 来回调了,在尝试中笔者 NSInvocation 的效率的确会低一点。用 block 可以很灵活的指定好需要调用什么方法。或许,也可以通过实现一个 weak proxy 的方式使用 Notification 来实现,笔者就没有尝试了,感兴趣的读者可以试试。
源码地址:https://github.com/AlbertXYZ/ThemeDemo
网友评论