Skip to content
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

[UIKit Bug] App 处于后台时修改 UIAppearance 里 UIImage 类型的属性很大几率导致第三方键盘 crash #1281

Closed
MoLice opened this issue Aug 7, 2021 · 3 comments
Assignees

Comments

@MoLice
Copy link
Collaborator

MoLice commented Aug 7, 2021

Bug 表现

  1. 系统设置里只保留一个键盘,且为第三方键盘,如搜狗、百度(这一步非必须,只是为了便于观察现象)。
  2. 在使用 QMUI 的 App 里(例如 QMUI Demo、微信读书、企业微信等大型应用)唤起该第三方键盘。
  3. 在另一个没使用 QMUI 的 App 里(例如系统的 App)唤起该第三方键盘。
  4. 不断重复在两个 App 之间切换,过一会即可看到第三方键盘无法显示,被一个系统默认键盘代替(意味着此时第三方键盘被杀掉,且系统尝试重新创建它时无法在足够快的时间内完成,于是用系统默认键盘代替),如以下录屏所示:
    crash.mov

预期的表现
不管进行多少次 App 间的切换,都不应该导致第三方输入法频繁被杀掉。

其他信息

  • 设备: All
  • iOS 版本: All
  • Xcode 版本: All
@jiasongs
Copy link

jiasongs commented Aug 7, 2021

我也遇到了这个问题,我屏蔽了[在后台切换主题]的代码之后就没有用户反馈过了。不知是不是同样的问题。

@MoLice
Copy link
Collaborator Author

MoLice commented Aug 7, 2021

连接真机,通过系统控制台可得知在出现 bug 现象时,第三方输入法会收到内存警告,在没使用 QMUI 的 App 之间互相切换不会引起这个内存警告,证明是 QMUI 里的某些代码引起该问题。

自己创建一个 Custom Keyboard Extension,按照 issue 的重现步骤操作后,我们的输入法确实遇到了系统内存限制的报错(上限 66MB,不同的 iOS 版本、iPhone 设备,该上限不同,所以老旧机器更容易触发该现象)。

在不断重复的测试过程中可看到,宿主应用(Host App,也即当前正在使用键盘的 App)更新 UIAppearance 的行为会通信给第三方键盘:

进而对 UIAppearance 中 UIImage 类型的对象进行反序列化:

通过在第三方键盘 Target 里 hook -[UIImage initWithCoder:] 可知在 Host App 退到后台时,第三方键盘里进行了上千次的图片生成操作,而且每次都比上一次的次数要翻很多倍。

而且按地址过滤后可看到其中有大量重复的 image 操作,这现象明显是异常的。

以 QMUI Demo 为例,通过这些图片的 size(width = 4, height = 88) 可知是 UINavigationBarbackgroundImageshadowImage 等图片,也即业务 App 自己设置的图片。而 QMUI Demo 里是在配置表里设置这些图片的,当系统的 DarkMode 发生切换时,会重新应用配置表,将图片设置到 UINavigationBar.appearance 的同时,遍历当前界面里的所有 UINavigationBar 实例,修改它们的样式。

结合上述的 crash 堆栈,尝试将上面截图里的①注释掉,发现问题得到解决。

将 backgroundImage 以 UINavigationBar.appearance 的方式使用是一个常规的操作,本身应该没问题,而问题出现在切换 App 时,此时 QMUI 会重新应用配置表,再走一次 UIAppearance 的更新,猜测可能是时机问题引起的。于是新建一个空项目,在 - [UIViewController traitCollectionDidChange:] 里设置 UIAppearance,当切换 App 时,系统会将 App 快速切换到 Light、Dark,分别对 App 截两张图,此时会触发 traitCollectionDidChange:,进而触发 UIAppearance 的更新,然后成功重现该问题。点击下载测试 Demo

到这里该 Bug 可以定性为系统 Bug:在 App 处于后台时修改 UIAppearance 里的 UIImage 类型的属性,会导致第三方键盘在短时间内异常进行大量 UIImage 的反序列化操作,最终触达系统对输入法分配的内存上限,输入法 crash。

进一步测试可知几个信息点:

  1. 通过 UIAppearance 设置的任意 image 都会在第三方输入法的第二次及后续的启动时反序列化(-[UIImage initWithCoder:]),设置多少次 appearance 的 image 就会调用多少次 initWithCoder。即便对同一个 UIAppearance 设置相同属性的 image,也不是覆盖性质,而是增加,每设置一次都会产生一次 initWithCoder:。
  2. 第一次显示键盘不会触发,第二次开始及后续都会触发。如果键盘正在显示时切换 App(也即把之前的 App 推到后台),那么重新唤醒该 App 时,键盘视为一次新的显示。
  3. 正常情况下使用 UIAppearance 也会触发第三方键盘调用 initWithCoder:,但设置多少次就会调用多少次,这通常不会导致 OOM,也不会像 issue 里描述的那样以几倍的规模扩大,且倍数还越来越大。

至此该系统 Bug 的解决方式就很清晰了:在设置 UIAppearance 前判断当前 App 是否在后台(建议监听 UIApplicationDidFinishLaunchingNotification、UIApplicationWillEnterForegroundNotification 等通知,而非判断 UIApplicationState,因为当 App 启动时走到某些类的 +load 方法时,UIApplicationState 为 Active,但走到 AppDelegate 的 didFinishLaunching 时又变成 Inactive 了),是则不设置。
由于该问题没有合适的统一修复的方法,因此 QMUI 仅修复自身的场景,不提供通用的方案。业务项目里非 QMUI 的代码如果也有这种操作,也需自行判断。

@MoLice
Copy link
Collaborator Author

MoLice commented Aug 31, 2021

已发布 4.3.0 修复该问题。

@MoLice MoLice closed this as completed Aug 31, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants