New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

QMUIConfigurationTemplate 没有保证执行时机 #264

Closed
MoLice opened this Issue Jan 4, 2018 · 9 comments

Comments

Projects
None yet
3 participants
@MoLice
Member

MoLice commented Jan 4, 2018

请填写运行环境

  • 设备:模拟器
  • 系统:iOS 11.2
  • Xcode 版本:9.2
  • QMUI iOS 版本:2.2.2

请描述具体问题

某些 QMUI 控件在用到配置表的值时配置表尚未初始化完成,导致控件的 UI 错误。

按照文档,目前 QMUI 的配置表使用时机是在项目的 application:didFinishLaunchingWithOptions: 里,但很多 QMUI 控件的 appearance 是在 + initialize 里就使用配置表的值了,此时配置表尚未设置好,导致这时候的值错误。

@MoLice

This comment has been minimized.

Member

MoLice commented Jan 4, 2018

原因分析

之前配置表其实有一个隐性的要求,就是配置表的应用时机必须必任何使用配置表的时机都要早,但在代码设计层面我们并没有措施保证这一点,仅仅只是在文档里要求配置表在 application:didFinishLaunchingWithOptions: 里运行(我们认为这个时机已经足够早)。但其实很多 QMUI 控件会在自己的 +initialize 里使用配置表的值去设置 appearance,而这个时机不确定,可能比 application:didFinishLaunchingWithOptions: 要早,就会导致 appearance 的值错误。

解决方法

  1. 配置表里的值的使用是从宏 QMUICMI 开始的,因此只要在 QMUICMI 被使用之前保证配置表的应用就行,于是我们把宏定义改为:
#define QMUICMI ({[[QMUIConfiguration sharedInstance] applyInitialTemplate];[QMUIConfiguration sharedInstance];})
  1. 由于 QMUI 自己保证配置表的执行时机,因此不需要使用者手动去调用了,因此将配置表改为自动运行。为此我们增加了 QMUIConfigurationTemplateProtocol 用于规定一份配置表应该具备的接口,并依靠“某个 class 名是否包含 QMUIConfigurationTemplate 字样,并且实现这个 protocol”来自动找到配置表的 class。
  2. 为了更精准的表意清晰,我们重命名了原本的 + setupConfigurationTemplate 方法为 - applyConfigurationTemplate

旧版 QMUI 配置表使用者的变动

  1. 让项目里原有的配置表 class 实现 QMUIConfigurationTemplateProtocol,并检查其命名是否有包含 QMUIConfigurationTemplate
  2. 将方法 + setupConfigurationTemplate 重命名为 - applyConfigurationTemplate,注意这个方法从类方法变为实例方法了。
  3. 在配置表里增加方法 shouldApplyTemplateAutomatically,用于告诉 QMUI 希望自动执行这份配置表。如果你的项目里存在多份配置表,则只应有一份配置表的 shouldApplyTemplateAutomatically 方法返回 YES
- (BOOL)shouldApplyTemplateAutomatically {
    return YES;
}
  1. 去掉原本在 application:didFinishLaunchingWithOptions: 里使用配置表的代码,现在配置表只需要保证被编译就可以了,不需要在任何地方 import,也不需要显式调用。

@MoLice MoLice added the bug label Jan 6, 2018

@MoLice MoLice closed this in 1613a08 Jan 8, 2018

@AFlamen

This comment has been minimized.

AFlamen commented Apr 11, 2018

我使用的cocoPods安装但是导入QMUIConfigurationTemplate.h和.m文件后,@interface QMUIConfigurationTemplate : NSObject 报错No type or protocol named 'QMUIConfigurationTemplateProtocol'

@MoLice

This comment has been minimized.

Member

MoLice commented Apr 11, 2018

@AFlamen 你的 QMUIKit 并没有默认 import?没有的话请尝试在 QMUIConfigurationTemplate.h 顶部写上:
#import <QMUIKit/QMUIKit.h>

@AFlamen

This comment has been minimized.

AFlamen commented Apr 12, 2018

我的PrefixHeader.pch文件已经导入头文件了啊,我先是使用cocoPods安装 pod 'QMUIKit',然后在我的Project Navigator导入QMUIConfigurationTemplate.h和.m两个文件,最后再到PrefixHeader.pch导入头文件#import <QMUIKit/QMUIKit.h>,运行发现报错No type or protocol named 'QMUIConfigurationTemplateProtocol',请问还需其它操作吗?

@MoLice

This comment has been minimized.

Member

MoLice commented Apr 12, 2018

@AFlamen 你这样的操作是没问题的,方便的话请将示例项目打包 zip 发给 qmuiteam@qq.com 我们看看?

@tbfungeek

This comment has been minimized.

tbfungeek commented Jul 17, 2018

您好 QMUIConfiguration applyInitialTemplate 这个方法会便利整个项目的所有类 如果整个项目类文件很多的时候这个遍历会不会很耗时会不会影响性能?

Protocol *protocol = @protocol(QMUIConfigurationTemplateProtocol);
int numberOfClasses = objc_getClassList(NULL, 0);
if (numberOfClasses > 0) {
Class *classes = (__unsafe_unretained Class *)malloc(sizeof(Class) * numberOfClasses);
numberOfClasses = objc_getClassList(classes, numberOfClasses);
for (int i = 0; i < numberOfClasses; i++) {
Class class = classes[i];
// 这里用 containsString 是考虑到 Swift 里 className 由“项目前缀+class 名”组成,如果用 hasPrefix 就无法判断了
if ([NSStringFromClass(class) containsString:@"QMUIConfigurationTemplate"] && [class conformsToProtocol:protocol]) {
if ([class instancesRespondToSelector:@selector(shouldApplyTemplateAutomatically)]) {
id template = [[class alloc] init];
if ([template shouldApplyTemplateAutomatically]) {
QMUI_hasAppliedInitialTemplate = YES;
[template applyConfigurationTemplate];
// 只应用第一个 shouldApplyTemplateAutomatically 的主题
break;
}
}
}
}
free(classes);
}

@tbfungeek

This comment has been minimized.

tbfungeek commented Jul 17, 2018

@MoLice

This comment has been minimized.

Member

MoLice commented Jul 17, 2018

@tbfungeek 以我们的项目为例,总 Class 数量为 27808 个,遍历到第 888 个文件即找到配置表,整个过程耗时 0.06秒,基本忽略不计。

以上测试结果基于 iPhone 7 真机,iOS 11.4。

另外有个小技巧,如果担心极限情况下遍历到 27807 个文件时才找到配置表,那么你可以在你项目的 Build Phases->Compile Sources 里找到你自己的 QMUIConfigurationTemplate.m,把它拖到列表的最底部,即可让它优先被遍历到,不过这步优化带来的收益更多可能是心理层面的。

@tbfungeek

This comment has been minimized.

tbfungeek commented Jul 18, 2018

@MoLice 谢谢

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment