Objective-C创建PDF映射文件求助:解决大PDF加载缓慢问题
用内存映射文件优化UIDocumentInteractionController加载大PDF的速度
我明白你遇到的痛点——大体积PDF(比如5.5MB)用UIDocumentInteractionController加载时耗时太久,而内存映射(mapped file)正是解决这个问题的关键。它能让系统按需加载文件内容,不用一次性把整个文件塞进内存,下面我给你一步步讲具体实现:
核心原理
内存映射是把磁盘文件直接映射到应用的虚拟内存空间,读取文件时系统会自动按需加载对应区块,避免了传统IO操作一次性读取整个大文件的开销,自然能加快UIDocumentInteractionController的加载速度。
Swift 实现步骤
1. 创建内存映射的PDF副本
我们需要把原始PDF文件通过内存映射的方式复制一份,这样后续加载时就能利用内存映射的优势:
import Foundation func createMappedPDF(from originalPDFURL: URL, to destinationURL: URL) throws { // 确保目标文件夹存在 try FileManager.default.createDirectory( at: destinationURL.deletingLastPathComponent(), withIntermediateDirectories: true ) // 获取原始文件大小 let fileAttrs = try FileManager.default.attributesOfItem(atPath: originalPDFURL.path) guard let fileSize = fileAttrs[.size] as? UInt64 else { throw NSError( domain: "PDFMappingError", code: 1, userInfo: [NSLocalizedDescriptionKey: "无法获取原始PDF文件大小"] ) } // 创建空的目标文件并设置对应大小 FileManager.default.createFile(atPath: destinationURL.path, contents: nil) let writeHandle = try FileHandle(forWritingTo: destinationURL) try writeHandle.truncate(atOffset: Int64(fileSize)) writeHandle.closeFile() // 打开源文件和目标文件,获取内存映射 let readHandle = try FileHandle(forReadingFrom: originalPDFURL) let mappedWriteHandle = try FileHandle(forWritingTo: destinationURL) guard let sourceMapping = readHandle.mappedFileContent, let destMapping = mappedWriteHandle.mappedFileContent else { throw NSError( domain: "PDFMappingError", code: 2, userInfo: [NSLocalizedDescriptionKey: "无法创建文件内存映射"] ) } // 直接复制内存映射内容 destMapping.copyMemory(from: sourceMapping) // 关闭文件句柄 readHandle.closeFile() mappedWriteHandle.closeFile() }
2. 用映射后的PDF展示预览
拿到映射后的文件URL后,直接传给UIDocumentInteractionController即可:
import UIKit func presentPDFPreview(with mappedPDFURL: URL, from viewController: UIViewController) { let docController = UIDocumentInteractionController(url: mappedPDFURL) docController.delegate = viewController docController.presentPreview(animated: true) }
3. 额外注意事项
- 权限检查:确保目标目录(比如
Documents文件夹)是可读写的,可以用FileManager.default.isWritableFile(atPath:)提前验证。 - 清理临时文件:如果映射后的PDF只是临时使用,记得在预览结束后删除它,避免占用磁盘空间:
try? FileManager.default.removeItem(at: mappedPDFURL)
Objective-C 版本实现
如果你还在用Objective-C,对应的代码如下:
创建内存映射PDF
#import <Foundation/Foundation.h> NSError *createMappedPDFFromURL(NSURL *originalURL, NSURL *destinationURL) { // 创建目标目录 [[NSFileManager defaultManager] createDirectoryAtURL:[destinationURL URLByDeletingLastPathComponent] withIntermediateDirectories:YES attributes:nil error:nil]; // 获取原始文件大小 NSDictionary *fileAttrs = [[NSFileManager defaultManager] attributesOfItemAtPath:originalURL.path error:nil]; unsigned long long fileSize = [fileAttrs[NSFileSize] unsignedLongLongValue]; // 创建空目标文件并设置大小 [[NSFileManager defaultManager] createFileAtPath:destinationURL.path contents:nil attributes:nil]; NSFileHandle *writeHandle = [NSFileHandle fileHandleForWritingToURL:destinationURL error:nil]; [writeHandle truncateFileAtOffset:(off_t)fileSize]; [writeHandle closeFile]; // 打开文件并创建内存映射 NSFileHandle *readHandle = [NSFileHandle fileHandleForReadingFromURL:originalURL error:nil]; NSFileHandle *mappedWriteHandle = [NSFileHandle fileHandleForWritingToURL:destinationURL error:nil]; NSData *sourceMapping = [readHandle mappedFileContent]; NSData *destMapping = [mappedWriteHandle mappedFileContent]; if (!sourceMapping || !destMapping) { return [NSError errorWithDomain:@"PDFMappingError" code:2 userInfo:@{NSLocalizedDescriptionKey:@"无法创建文件内存映射"}]; } // 复制映射内容 memcpy((void *)destMapping.bytes, sourceMapping.bytes, sourceMapping.length); // 关闭句柄 [readHandle closeFile]; [mappedWriteHandle closeFile]; return nil; }
展示PDF预览
#import <UIKit/UIKit.h> void presentPDFPreviewWithMappedURL(NSURL *mappedURL, UIViewController *vc) { UIDocumentInteractionController *docController = [UIDocumentInteractionController interactionControllerWithURL:mappedURL]; docController.delegate = vc; [docController presentPreviewAnimated:YES]; }
这样实现后,UIDocumentInteractionController加载大PDF时就不会因为一次性读取整个文件而卡顿了,内存映射会帮你按需加载所需的文件内容。
内容的提问来源于stack exchange,提问作者user979331
相关产品推荐
相关产品推荐

