iOS NSAttributedString NSHTMLTextDocumentType陷阱 / 2019-11-13

当你看到这篇文章时,很可能你也遇到了NSAttributedString的initWithData:options:documentAttributes:error:接口初始化HTML字符串的问题,这个接口的已知问题有:耗时较长,偶现crash,在不同版本系统上的表现不一致等。

当你看到这篇文章时,很可能你也遇到了NSAttributedString的initWithData:options:documentAttributes:error:接口初始化HTML字符串的问题,这个接口的已知问题有:耗时较长,偶现crash,在不同版本系统上的表现不一致等。实际上这类问题我很早之前就了解到过,只是没想到的是即使到了iOS 13苹果还没有很好的解决这些问题。

initWithData接口最大的问题就是初始化HTML字符串时耗时很大。我测试的iPhone 6手机,处理一段HTML文本时耗时达到惊人的200ms,并且在一款iOS 12.4.1系统上,偶现超时的问题,导致UI卡死。

为防止阻塞主线程,可以尝试切换到非主线程执行:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
	NSDictionary *attribute = @{NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType};
	NSAttributedString *attrStr = [[NSAttributedString alloc] initWithData:htmlData] options:attribute documentAttributes:nil error:nil];
	dispatch_async(dispatch_get_main_queue(), ^{
		self.label.attributedText = attrStr;
	});
});

上面的代码在iOS 9及以上系统都没问题(至少我目前测试的包括各个主版本号的系统都没问题),但在iOS 8系统上直接crash。来看看这个接口的使用说明:

The HTML importer should not be called from a background thread (that is, the options dictionary includes NSDocumentTypeDocumentAttribute with a value of NSHTMLTextDocumentType). It will try to synchronize with the main thread, fail, and time out. Calling it from the main thread works (but can still time out if the HTML contains references to external resources, which should be avoided at all costs). The HTML import mechanism is meant for implementing something like markdown (that is, text styles, colors, and so on), not for general HTML import.

这段说明的大致意思是:当使用NSHTMLTextDocumentType参数时,应该在主线程调用。如果HTML代码中引用有外部资源,调用会超时。实际验证就会发现这段说明很有迷惑性:在iOS 8系统上表现确实和接口说明一致,在iOS 9及以上的系统即使切换到global queue也可以良好运行,而调用耗时的问题各个系统都存在。

网络上有很多关于这类问题的讨论:
NSMutableAttributedString initialisation crash
NSAttributedString NSHTMLTextDocumentType

这个问题目前没有更好的处理方式,只能尽量不使用NSAttributedString的初始化接口解析HTML字符串。如果确有场景必须使用,建议做如下处理:

  • 使用DTCoreText等第三方实现

我并未实际使用,无法确定DTCoreText效率比原生接口高多少。

  • 使用NSAttributedString

区分iOS 8和iOS 8以上系统:iOS 8系统直接调用(iOS 8用户已经很少了,可以考虑不优化);iOS 9及以上系统采取切换到非主线程的方式。

其它文章

iOS 13 Scene Delegate and multiple windows
iOS Sign With Apple实践
iOS 13中dyld 3的改进和优化
iOS 13 适配
iOS Asset Catalog and Bundle