💡这是字节面试出的题目,当时连UIAppearance怎么使用的都不知道,没回答出来,感觉愧对江东父老,特反省一下,写一个 demo 大家看看对不对
代码地址可查看:https://github.com/irobbin1024/UIAppearance-demo
一. UIAppearance是什么
UIAppearance是一个可以给 App 进行定制主题的工具
传统上,如果我们要全局改变按钮的样式,例如背景色改为灰色,还是比较麻烦的,可以采取的方式,例如写一个 UIButton 的子类,在子类中进行变更。使用UIAppearance就比较简单了。看一下它的 API
1 | [[UIButton appearance] setBackgroundColor:[UIColor grayColor] forState:UIControlStateNormal]; |
如上所示,一行代码就搞定了,我们要做的就是这个效果。
接下来看一下我的 API
1 | [[MyView xy_appearance] setBackgroundColor:[UIColor redColor]]; |
二. 分析要点
要实现UIAppearance的效果,有两点要注意的
第一就是针对目标 API 的调用,例如setBackgroundColor,我们可以换成任意的一个属于UIButton的方法,例如setAlpha。
第二就是我们在配置好效果之后,到底怎么应用到 UI 上,这里我们不讨论系统到底是怎么实现的,根据我的思路就是hook UIView 的 addSubView:方法,在添加之后调用刚才配置的方法即可
三. 具体的实现
1. API 调用
这里我们采用欺骗的方式来实现,xy_appearance方法会返回一个 instanceType,对于编译器来说,他会认为返回的是一个MyView的实例,我们也就可以调用任何 MyView 的方法了,顺便说一下,MyView 是一个 UIView 子类
首先,我们定义一个协议XYUIAppearance,里面是xy_appearance方法,MyView 实现这个协议
在外部调用 MyView 的方法,例如setBackgroundColor的时候,我们将方法保存起来,在添加到父类的时候进行调用。
为了让这一步更简单,我使用了 NSProxy,NSProxy是一个代理类,可以实现对其方法调用的捕获,再转发给被代理的对象。我们可以在捕获之后,保存起来
接下来是具体的代码
XYUIAppearance.h 👇
1 | @protocol XYUIAppearance <NSObject> |
MyView.h 👇
1 | @interface MyView : UIView<XYUIAppearance> |
MyView.m👇
1 | @implementation MyView |
XYProxy.h👇
1 | @interface XYProxy : NSProxy |
XYProxy.m👇
1 | @interface XYProxy () |
2. 应用效果
这里就比较简单了,首先 hook UIView 的 addSubView 方法,然后找到对应的 NSInvocation 进行调用。
我这里写了一个类XYAppearanceManager完成NSInvocation保存任务,一个 UIView 的分类来完成 hook 的任务
可以看一下具体的代码
1 | - (void)hook_addSubview:(UIView *)view { |
1 | @implementation XYAppearanceManager |