Objective-C 分享扩展场景下NSURL转视频PHAsset返回nil的实现问题
问题场景
在开发视频类Share Extension功能时,通过itemProvider的completionHandler可以拿到视频对应的NSURL对象,但现有业务发送接口仅支持传入PHAsset类型参数。当前实现中调用fetchAssetsWithALAssetURLs查询对应PHAsset时,返回结果始终为空,无法获取有效资产对象传入接口。
现有核心代码逻辑:
[itemProvider loadItemForTypeIdentifier:UTTypeVideo options:nil completionHandler:^(NSURL *videoURL, NSError *error) { NSURL *url = videoURL; // 此步骤始终返回空结果 PHFetchResult *fetchResult = [PHAsset fetchAssetsWithALAssetURLs:@[url] options:nil]; PHAsset *asset = [fetchResult firstObject]; AVAsset *videoAsset = [AVAsset assetWithURL:videoURL]; // 卡点位置:传入nil的asset调用发送接口 [[ChatMessenger sharedManager] sendVideoMessageWithAsset:asset caption:captionString room:roomData start:^(MessageModel * _Nonnull message) { NSLog(@"======START"); } progress:^(CGFloat progress, CGFloat total) { NSLog(@"======PROGRESS %f TO %f", progress, total); } success:^(MessageModel *message) { NSLog(@"======SUCCESS"); [self finishSharedItemRequestReturningItems]; } failure:^(NSError *error) { NSLog(@"======FAILED"); }]; }];
问题根因
fetchAssetsWithALAssetURLs是专门匹配旧版ALAsset框架的assets-library://格式URL的接口,只用于查询系统相册内已存在的资源。
而Share Extension从itemProvider拿到的UTTypeVideo对应的NSURL,大多是分享插件沙盒内的临时文件路径(file://开头),和系统相册的资源没有映射关系,自然查不到对应的PHAsset。同时Share Extension和主App沙盒相互隔离,靠临时文件路径反向关联系统相册资源的路径本身就是走不通的。
落地实现步骤
既然拿到的是本地临时文件URL,不要尝试反向查系统相册的已有资产,正确的路径是:把临时视频文件临时写入系统相册生成新的PHAsset,传入业务接口完成发送后,再删除临时写入的相册资源,避免占用用户存储空间。
- 先申请临时文件的访问权限:Share Extension拿到的受保护临时文件需要手动触发权限申请,否则读取文件会失败:
// 申请临时文件访问权限 BOOL hasAccess = [videoURL startAccessingSecurityScopedResource]; if (!hasAccess) { // 权限申请失败直接走分享错误流程 return; } - 调用PHPhotoLibrary变更接口,把临时视频写入相册生成对应资产:
__block PHObjectPlaceholder *tempAssetPlaceholder; [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{ PHAssetCreationRequest *createRequest = [PHAssetCreationRequest creationRequestForAssetFromVideoAtFileURL:videoURL]; // 保留创建后的临时资产占位符,用于后续获取真实PHAsset tempAssetPlaceholder = createRequest.placeholderForCreatedAsset; } completionHandler:^(BOOL success, NSError * _Nullable error) { // 用完临时文件后及时释放权限,避免资源泄漏 [videoURL stopAccessingSecurityScopedResource]; if (!success) { // 写入相册失败走错误分支 return; } // 根据占位符的本地标识,获取真实的PHAsset对象 PHFetchResult *result = [PHAsset fetchAssetsWithLocalIdentifiers:@[tempAssetPlaceholder.localIdentifier] options:nil]; PHAsset *tempAsset = result.firstObject; if (!tempAsset) { // 资产获取失败走错误分支 return; } // 切主线程调用业务发送接口 dispatch_async(dispatch_get_main_queue(), ^{ [[ChatMessenger sharedManager] sendVideoMessageWithAsset:tempAsset caption:captionString room:roomData start:^(MessageModel * _Nonnull message) { NSLog(@"======START"); } progress:^(CGFloat progress, CGFloat progressTotal) { NSLog(@"======PROGRESS %f TO %f", progress, progressTotal); } success:^(MessageModel *message) { NSLog(@"======SUCCESS"); // 发送完成后清理临时写入的相册资源 [self removeTempAssetFromLibrary:tempAsset]; [self finishSharedItemRequestReturningItems]; } failure:^(NSError *error) { NSLog(@"======FAILED"); // 发送失败同样清理临时资源 [self removeTempAssetFromLibrary:tempAsset]; }]; }); }]; - 临时资源清理逻辑实现:
- (void)removeTempAssetFromLibrary:(PHAsset *)tempAsset { [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{ [PHAssetChangeRequest deleteAssets:@[tempAsset]]; } completionHandler:^(BOOL success, NSError * _Nullable error) { // 清理完成无需额外处理,避免残留临时文件占用用户存储 }]; }
注意事项
- 确保Share Extension的Target已经在Capabilities中开启
Photos权限,和主App的权限配置保持一致,否则写入和删除相册资源的接口会直接调用失败。 - 绝对不要尝试通过file类型URL直接转PHAsset,系统没有提供这类转换API,反向查询的思路从根上就是不通的。
- 所有涉及UI更新和业务接口调用的逻辑,必须切到主线程执行,避免多线程异常。
内容的提问来源于stack exchange,提问作者Axel
相关产品推荐
相关产品推荐

