iOS:为NSData的writeToFile添加完成回调以显示PDF文件
嘿,这个需求其实很明确——就是要保证文件100%写入完成后再启动预览,避免出现文件还没写完就去读取导致的预览失败或者崩溃问题。下面给你两种实用的实现方式,你可以根据自己的iOS版本选择:
方案1:用GCD实现异步写入+完成回调
直接在主线程调用writeToFile:atomically:会阻塞UI,所以我们把写入操作放到后台队列执行,完成后再切回主线程处理预览逻辑(UI操作必须在主线程):
// 假设你已经拿到了PDF的NSData对象pdfData NSString *filePath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"temp.pdf"]; // 后台队列执行写入操作 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ BOOL writeSuccess = [pdfData writeToFile:filePath atomically:YES]; // 切回主线程处理UI逻辑 dispatch_async(dispatch_get_main_queue(), ^{ if (writeSuccess) { // 文件写入成功,启动QLPreviewController QLPreviewController *previewVC = [[QLPreviewController alloc] init]; previewVC.dataSource = self; previewVC.currentPreviewItemIndex = 0; [self presentViewController:previewVC animated:YES completion:nil]; } else { // 写入失败,给用户提示 UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"提示" message:@"PDF文件保存失败" preferredStyle:UIAlertControllerStyleAlert]; [alert addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:nil]]; [self presentViewController:alert animated:YES completion:nil]; } }); }); // 别忘了实现QLPreviewControllerDataSource协议方法 - (NSInteger)numberOfPreviewItemsInPreviewController:(QLPreviewController *)controller { return 1; } - (id<QLPreviewItem>)previewController:(QLPreviewController *)controller previewItemAtIndex:(NSInteger)index { NSString *filePath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"temp.pdf"]; return [NSURL fileURLWithPath:filePath]; }
方案2:用FileManager的现代异步写入API(iOS 13+)
苹果在iOS13之后提供了更简洁的异步写入API,自带完成回调,不用手动管理GCD队列,代码更清爽:
// 准备好pdfData和文件路径 NSString *filePath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"temp.pdf"]; NSURL *fileURL = [NSURL fileURLWithPath:filePath]; // 异步写入,完成后触发回调 [pdfData writeToURL:fileURL options:NSDataWritingAtomic completionHandler:^(NSError * _Nullable error) { // 切回主线程处理UI dispatch_async(dispatch_get_main_queue(), ^{ if (!error) { // 写入成功,启动预览 QLPreviewController *previewVC = [[QLPreviewController alloc] init]; previewVC.dataSource = self; previewVC.currentPreviewItemIndex = 0; [self presentViewController:previewVC animated:YES completion:nil]; } else { // 处理写入错误 UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"错误" message:[error localizedDescription] preferredStyle:UIAlertControllerStyleAlert]; [alert addAction:[UIAlertAction actionWithTitle:@"确定" style:UIAlertActionStyleDefault handler:nil]]; [self presentViewController:alert animated:YES completion:nil]; } }); }]; // 同样需要实现QLPreviewControllerDataSource协议方法(同方案1)
几个关键注意点:
- 所有UI操作(比如弹出预览控制器、提示框)必须放在主线程执行,否则可能出现UI异常或者崩溃。
- 开启
NSDataWritingAtomic(也就是atomically:YES)会先把文件写入临时文件,完成后再替换目标文件,能避免写入中断导致的文件损坏,建议始终开启。 - 如果需要重复写入同文件名的PDF,建议每次写入前检查文件是否存在,必要时删除旧文件,避免冲突。
内容的提问来源于stack exchange,提问作者user979331
相关产品推荐
相关产品推荐

