You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Objective-C 分享扩展场景下NSURL转视频PHAsset返回nil的实现问题

Share Extension 视频分享 NSURL 转 PHAsset 适配方案

问题场景

在开发视频类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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.25 13:24:03