You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

iOS 15使用HTML字符串初始化NSAttributedString主线程崩溃问题

iOS15下HTML转NSAttributedString主线程崩溃问题

问题描述

我实现了一个将HTML字符串转换为NSAttributedString的扩展,iOS 15发布后收到大量用户崩溃上报,所有崩溃均发生在iOS 15版本的主线程上,使用Xcode 12.5进行发布版本的归档打包。

典型崩溃日志(来自Crashlytics)

Crashed: com.apple.main-thread
0  libsystem_platform.dylib       0x1f29d60c0 _os_unfair_lock_recursive_abort + 36
1  libsystem_platform.dylib       0x1f29d0a10 _os_unfair_lock_lock_slow + 304
2  Foundation                     0x183d5e730 -[NSProcessInfo(NSProcessInfoHardwareState) isLowPowerModeEnabled] + 68
3  WebCore                        0x192011004 <redacted> + 56
4  CoreFoundation                 0x182535ee8 __CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ + 28
5  CoreFoundation                 0x1825d1b9c ___CFXRegistrationPost_block_invoke + 52
6  CoreFoundation                 0x1825a4f54 _CFXRegistrationPost + 456
7  CoreFoundation                 0x18254bd54 _CFXNotificationPost + 716
8  Foundation                     0x183d50028 -[NSNotificationCenter postNotificationName:object:userInfo:] + 96
9  Foundation                     0x183dc89d4 NSProcessInfoNotifyPowerState + 188
10 Foundation                     0x183d5e768 -[NSProcessInfo(NSProcessInfoHardwareState) isLowPowerModeEnabled] + 124
11 WebCore                        0x191595118 <redacted> + 96
12 WebCore                        0x19201116c WebCore::LowPowerModeNotifier::LowPowerModeNotifier(WTF::Function<void (bool)>&&) + 52
13 WebCore                        0x192f36c4c WebCore::Page::Page(WebCore::PageConfiguration&&) + 1848
14 WebKitLegacy                   0x1a605ae34 -[WebView(WebPrivate) _commonInitializationWithFrameName:groupName:] + 3060
15 WebKitLegacy                   0x1a605a214 -[WebView(WebPrivate) _initWithFrame:frameName:groupName:] + 116
16 UIFoundation                   0x18d306028 -[NSHTMLReader _loadUsingWebKit] + 832
17 UIFoundation                   0x18d30715c -[NSHTMLReader attributedString] + 32
18 UIFoundation                   0x18d2c04e8 _NSReadAttributedStringFromURLOrData + 8420
19 UIFoundation                   0x18d2be378 -[NSAttributedString(NSAttributedStringUIFoundationAdditions) initWithData:options:documentAttributes:error:] + 156
20 MyAppExtensions                 0x108aa0fc4 _hidden#912_ + 4374482884 (__hidden#360_:4374482884)
21 MyAppExtensions                 0x108aa0894 NSAttributedString.init(htmlString:font:useDocumentFontSize:) + 36 (__hidden#925_:36)
22 MyAppName                           0x1045eca18 {foo_class}.convertHTML(_:) + 670 (xxxx.swift:670)
23 MyAppName                           0x1045e5c58 {foo_class}.some_function_3 + 620 (xxxx.swift:620)
24 MyAppName                           0x1045e4760 {foo_class}.some_function_2 + 212 (xxxx.swift:212)
25 MyAppName                           0x1045e4200 {foo_class}.some_function_1 + 138 (xxxx.swift:138)
.
.
.
42 UIKitCore                      0x184b5e1d0 _UIGestureRecognizerSendTargetActions + 116
43 UIKitCore                      0x184b2705c _UIGestureRecognizerSendActions + 284
44 UIKitCore                      0x184b60580 -[UIGestureRecognizer _updateGestureForActiveEvents] + 636
45 UIKitCore                      0x184b186fc _UIGestureEnvironmentUpdate + 1988
46 CoreFoundation                 0x18254c570 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 36
47 CoreFoundation                 0x18251b854 __CFRunLoopDoObservers + 572
48 CoreFoundation                 0x1825168ec __CFRunLoopRun + 1052
49 CoreFoundation                 0x18252a3c8 CFRunLoopRunSpecific + 600
50 GraphicsServices               0x19dd3b38c GSEventRunModal + 164
51 UIKitCore                      0x184ed00bc -[UIApplication _run] + 1100
52 UIKitCore                      0x184c4dbe8 UIApplicationMain + 2124
53 libswiftUIKit.dylib            0x199afe184 UIApplicationMain(_:_:_:_:) + 104
54 MyAppName                      0x1043a5894 main + 7 (main.swift:7)
55 dyld                           0x106b81a24 start

现有HTML转NSAttributedString实现代码

extension NSAttributedString {
  convenience public init(htmlString html: String, font: UIFont? = nil, useDocumentFontSize: Bool = true) throws {
    let options: [NSAttributedString.DocumentReadingOptionKey: Any] = [
      .documentType: NSAttributedString.DocumentType.html,
      .characterEncoding: String.Encoding.utf8.rawValue
    ]

    guard let data = html.data(using: .utf8, allowLossyConversion: true) else {
      self.init(string: "")
      return
    }

    guard let fontFamily = font?.familyName else {
      try self.init(data: data, options: options, documentAttributes: nil)
      return
    }

    let attr = try NSMutableAttributedString(data: data, options: options, documentAttributes: nil)

    let fontSize: CGFloat? = useDocumentFontSize ? nil : font!.pointSize
    let range = NSRange(location: 0, length: attr.length)
    attr.enumerateAttribute(.font, in: range, options: .longestEffectiveRangeNotRequired) { attrib, range, _ in
      if let htmlFont = attrib as? UIFont {
        let traits = htmlFont.fontDescriptor.symbolicTraits
        var descrip = htmlFont.fontDescriptor.withFamily(fontFamily)

        if (traits.rawValue & UIFontDescriptor.SymbolicTraits.traitBold.rawValue) != 0 {
          descrip = descrip.withSymbolicTraits(.traitBold)!
        }

        if (traits.rawValue & UIFontDescriptor.SymbolicTraits.traitItalic.rawValue) != 0 {
          descrip = descrip.withSymbolicTraits(.traitItalic)!
        }

        attr.addAttribute(.font, value: UIFont(descriptor: descrip, size: fontSize ?? htmlFont.pointSize), range: range)
      }
    }

    self.init(attributedString: attr)
  }
}

根因分析

这是Xcode12及更低版本打包的应用在iOS15上的兼容性系统Bug:系统原生HTML解析接口内部依赖WebKitLegacy组件,iOS15下该组件初始化时会监听低功耗模式切换通知,通知触发过程中出现了递归调用isLowPowerModeEnabled的逻辑,触发了不公平锁的递归加锁崩溃。

修复方案

  • 升级Xcode到13及以上版本打包,苹果已经在新的SDK中修复了该系统级问题,是成本最低的修复方案。
  • 若暂时无法升级打包环境,将HTML转NSAttributedString的操作放在子线程执行,转换完成后再切回主线程刷新UI,避开主线程的通知递归调用死锁。
  • 替换系统原生的HTML转NSAttributedString能力,使用第三方轻量HTML解析库实现转换逻辑,完全规避WebKit依赖带来的系统Bug。
  • 若业务场景对HTML格式支持要求不高,可以提前将HTML中的标签替换为本地富文本标记,避免调用系统的HTML解析接口。

内容的提问来源于stack exchange,提问作者Chen Zheng

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.02 04:06:03