ios - 在 iOS 中从 HTML 创建 PDF 时出现异常
问题描述
我有一个处理 HTML 字符串数组并使用以下方法逐页构建 PDF 的过程:
let printPageRenderer = ReportPrintPageRenderer(withTemplate: self.template)
var pageIndex = 0
data.forEach { (htmlPage) in
let printFormatter = UIMarkupTextPrintFormatter(markupText: htmlPage)
printPageRenderer.addPrintFormatter(printFormatter, startingAtPageAt: pageIndex)
pageIndex += 1
}
let pdfData = drawPDFUsingPrintPageRendererNoImages(printPageRenderer: printPageRenderer)
其中 drawPDFUsing... 方法使用了我多次描述的标准方法:
let data = NSMutableData()
UIGraphicsBeginPDFContextToData(data, CGRect.init(x: 0, y: 0, width: template.pageWidth, height: template.pageHeight), nil)
printPageRenderer.prepare(forDrawingPages: NSMakeRange(0, printPageRenderer.numberOfPages))
let bounds = UIGraphicsGetPDFContextBounds()
for i in 0...((printPageRenderer.numberOfPages - 1 < 0) ? 0 : printPageRenderer.numberOfPages - 1) {
UIGraphicsBeginPDFPage()
printPageRenderer.drawPage(at: i, in: bounds)
}
UIGraphicsEndPDFContext()
return data
整个过程完美运行,直到我获得大约 130 页长的 PDF 文档。在模拟器中,我可以毫无问题地打印超过 250 页的文档。但是,对于实际设备(iPhone x 或 iPad pro)上的相同数据,data.forEach 循环会一直运行,直到 pageIndex 达到大约 130,然后它会在此行显示以下信息失败:
let printFormatter = UIMarkupTextPrintFormatter(markupText: htmlPage)
Thread 1: EXC_BREAKPOINT (code=1, subcode=0x1aa2b0330)
warning: could not execute support code to read Objective-C class data in the process. This may reduce the quality of type information available.
不产生其他异常
内存使用量仍然可以,虽然它已经飙升,但考虑到我正在做的事情,它似乎并不过分。此外,在模拟器中运行时它会像这样增加,并且通常只有 300-400MB 左右的峰值。
我尝试了以下方法来解决这个问题,但都没有成功:
- 将流程的各个部分包装在 autoreleasepool 中
- 通过在处理数据时随机化数据,确保它不是我的 HTML 数据字符串。无论订单如何,它总是在 130 条记录后失败
- 切换到iOS13
- 更改将每个页面作为单独的 PDF 写入磁盘的过程。不管我怎么做,创建130个页面后仍然总是失败
我不知所措,因为它正在发生什么,并且不知道如何解决这个问题。任何想法或帮助将不胜感激。
根据评论中的建议,这是在调试器中运行 bt 的跟踪:
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BREAKPOINT (code=1, subcode=0x1aa326a14)
frame #0: 0x00000001aa326a14 WebCore`bmalloc::IsoAllocator<bmalloc::IsoConfig<88u> >::allocateSlow(bool) + 252
frame #1: 0x00000001aaaf7f48 WebCore`WebCore::RenderText::createTextBox() + 28
frame #2: 0x00000001aab06e90 WebCore`WebCore::RenderTextLineBoxes::createAndAppendLineBox(WebCore::RenderText&) + 40
frame #3: 0x00000001aa9e2c50 WebCore`WebCore::RenderBlockFlow::constructLine(WebCore::BidiRunList<WebCore::BidiRun>&, WebCore::LineInfo const&) + 344
frame #4: 0x00000001aa9e5944 WebCore`WebCore::RenderBlockFlow::createLineBoxesFromBidiRuns(unsigned int, WebCore::BidiRunList<WebCore::BidiRun>&, WebCore::InlineIterator const&, WebCore::LineInfo&, WebCore::VerticalPositionCache&, WebCore::BidiRun*, WTF::Vector<WebCore::WordMeasurement, 64ul, WTF::CrashOnOverflow, 16ul>&) + 100
frame #5: 0x00000001aa9e825c WebCore`WebCore::RenderBlockFlow::layoutRunsAndFloatsInRange(WebCore::LineLayoutState&, WebCore::BidiResolverWithIsolate<WebCore::InlineIterator, WebCore::BidiRun, WebCore::BidiIsolatedRun>&, WebCore::InlineIterator const&, WebCore::BidiStatus const&, unsigned int) + 4576
frame #6: 0x00000001aa9e5e2c WebCore`WebCore::RenderBlockFlow::layoutRunsAndFloats(WebCore::LineLayoutState&, bool) + 920
frame #7: 0x00000001aa9ea03c WebCore`WebCore::RenderBlockFlow::layoutLineBoxes(bool, WebCore::LayoutUnit&, WebCore::LayoutUnit&) + 1800
frame #8: 0x00000001aa9cd5cc WebCore`WebCore::RenderBlockFlow::layoutBlock(bool, WebCore::LayoutUnit) + 1056
frame #9: 0x00000001aaadad84 WebCore`WebCore::RenderTableCell::layout() + 196
frame #10: 0x00000001aaae8794 WebCore`WebCore::RenderTableRow::layout() + 252
frame #11: 0x00000001aaaea914 WebCore`WebCore::RenderTableSection::layout() + 844
frame #12: 0x00000001aaacf744 WebCore`WebCore::RenderTable::layout() + 1916
frame #13: 0x00000001aa9cf578 WebCore`WebCore::RenderBlockFlow::layoutBlockChild(WebCore::RenderBox&, WebCore::RenderBlockFlow::MarginInfo&, WebCore::LayoutUnit&,
.....
如果我将其剥离为最基本的要素,甚至不尝试创建 PDF:
func exportHTMLContentToPDFNoImages(reportName name:String, fromHTMLData data: [String]) throws -> URL? {
data.shuffled().forEach { (htmlPage) in
autoreleasepool { () -> Void in
let _ = UIMarkupTextPrintFormatter(markupText: htmlPage)
}
}
return nil
}
在第 130 次通过循环后它仍然崩溃。用一些简单的html替换htmlPage
<html><body>Hi</body></html>
工作得很好。所以我正在创建的表中一定有一些东西导致了这个问题。
更新
我能够将其简化为一个非常简单的示例,向我证明这是 iOS 中的一个错误。我也最终向苹果报告了它。幸运的是,这个错误看起来已经在 13.1 中修复,所以即使 iOS 12 存在问题,未来也有前进的道路。
解决方案
要么您的内存不足,要么堆已损坏。我的猜测是,尽管您的内存使用情况截图,但您的内存不足。
这EXC_BREAKPOINT
发生在 WebKit 的bmalloc::IsoAllocator
.
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BREAKPOINT (code=1, subcode=0x1aa326a14)
frame #0: 0x00000001aa326a14 WebCore`bmalloc::IsoAllocator<bmalloc::IsoConfig<88u> >::allocateSlow(bool) + 252
frame #1: 0x00000001aaaf7f48 WebCore`WebCore::RenderText::createTextBox() + 28
这是引发异常的行(请注意,这是来自 WebKit 中继,可能与您手机上运行的版本不同):
template<typename Config>
BNO_INLINE void* IsoAllocator<Config>::allocateSlow(bool abortOnFailure)
{
...
return m_freeList.allocate<Config>([] () { BCRASH(); return nullptr; });
}
m_freeList.allocate
如果无法从自己的列表中分配,则运行 lambda 中的内容(括号中的部分)。
BCRASH
引发您看到的异常。从BAssert.h:
// Crash with a SIGTRAP i.e EXC_BREAKPOINT.
// We are not using __builtin_trap because it is only guaranteed to abort, but not necessarily
// trigger a SIGTRAP. Instead, we use inline asm to ensure that we trigger the SIGTRAP.
#define BCRASH() do { \
BBreakpointTrap(); \
__builtin_unreachable(); \
} while (false)
此分配似乎是 WebKit 呈现 PDF 的一部分,但它可能会从您不断增长的NSMutableData
.
尝试使用UIGraphicsBeginPDFContextToFile
代替,UIGraphicsBeginPDFContextToData
看看是否仍然发生崩溃。有可能实现UIGraphicsBeginPDFContextToFile
将页面写入磁盘,而不是像您正在做的那样一次将整个结果全部放入内存中。
切换到 后UIGraphicsBeginPDFContextToFile
,您可以将 PDF 用作文件,或者,如果您需要加载到 中NSData
,使用此方法来使用内存映射文件:
let data = try Data(contentsOf: URL(fileURLWithPath: newPDFPath), options: .mappedIfSafe)
推荐阅读
- twitter-bootstrap - 如何修复图像的过渡缩放
- node.js - Redis SCAN 没有给出 NodeJS 和 node_redis 中的所有匹配项
- python - tox.ini 环境变量替换问题
- html - CSS/HTML 中的内衬标题
- python - 尝试从自定义应用程序导入模型时出现导入错误
- javascript - React-Leaflet - 自定义 Pin,OnClick 链接到另一个页面
- reactjs - 有什么方法可以将 react 的新上下文 api 与 react-redux 包一起使用?
- jquery - 将 jquery 验证规则添加到 ASP.NET MVC 视图时出现错误“无法读取未定义的属性‘设置’”
- excel - 使用 VBA 为图形创建图例
- c# - 实体框架 ASP.NET;HTTP POST:JSON 请求正文中存在必需的值 DbEntityValidationException