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

如何在Swift/Objective-C中验证Feed中的有效多媒体链接

问题整理

我正在构建一个包含多媒体资源链接的Feed,无效链接不能加入Feed——无法播放的视频或加载失败的图片会严重影响用户体验。Feed由设备通过JSON接口拉取,没有WebSocket支持。

我可以在将链接加入Feed前做验证,但验证会耗时,条目多的话Feed加载速度会变慢。就算接受延迟,我也没法在下载数据后的同步转换流程里实现异步调用链接验证。

当前工作流程:

  • 异步导入JSON为对象,示例JSON:{"feed":[{"title":"Check out this video","videosurl":"https://www.youtube.com/somevideo.mp4"}]}
  • 将JSON序列化为Feed对象数组(同步操作)
  • 将Feed对象数组保存至Core Data(同步操作)
  • 使用NSFetchedResultsController展示对象(同步操作)

我计划在步骤3保存到Core Data前验证每个条目链接,目前步骤3的代码(已改用Function B)如下:

-(void) saveFeed:(NSArray *)items inContext: (NSManagedObjectContext*) context
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Videofeed" inManagedObjectContext:context];
    id item;
    for (int i=0;i<items.count;i++){ 
        item = items[i];
        NSString *link = item.videosurl;
        __block BOOL validFile = NO;
        [link foundValidFileAtSurlWithCompletion:^(BOOL validFile) {
            // 无法将异步结果同步到外部的validFile变量
        }];
        if (!validFile) {
            // 想跳过无效条目,但当前逻辑不生效
            // continue;
        }
        
        NSManagedObject *record = [[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:context];
        [record setValue:item.title forKey:@"title"];
        [record setValue:item.videosurl forKey:@"videosurl"];
    }
    [context save:nil];
}

已实现的验证函数:

Function A(Swift async/await版本,iOS13+可用)

@objc public extension NSString {
    
    @available(iOS 13.0.0, *)
    func fileExists(at surl: NSString) async throws -> Bool {
        let str = self as String
            
        guard let url = URL(string: str) else { return false }
           
        var request = URLRequest(url: url)
        request.httpMethod = "HEAD"
        request.timeoutInterval = 1.0 // 可根据需求调整
        let (_, response) = try await URLSession.shared.data(for: request)
        return (response as? HTTPURLResponse)?.statusCode == 200
     }
}

Function B(带completionHandler的异步版本)

@objc public extension NSString {
    func foundValidFileAtSurl(completion:@escaping (Bool) -> ()) {
       
        let str = self as String
     
        if self == "" {
            completion(false)
            return
        }
        guard let url = URL(string: str) else {
            completion(false)
            return
        }
            
        var request = URLRequest(url: url)
        request.httpMethod = "HEAD"; // 只请求响应头,不下载完整文件
        request.timeoutInterval = 1.0 // 可根据需求调整
        let task = URLSession.shared.dataTask(with: request) { data, response, error in
            guard error == nil else {
                print(error ?? "未知错误", error!.localizedDescription)
                completion(false)
                return
            }
            completion((response as? HTTPURLResponse)?.statusCode == 200)
        }
        task.resume()
    }
}

核心疑问:我已经实现了用completionHandler检查URL有效性并返回BOOL的代码,但没法让同步的Core Data保存代码等待异步验证的响应。请问在Objective-C中该怎么实现?


解决方案

方法1:批量异步验证后统一保存(推荐)

不要在同步的for循环里等待异步结果,而是先批量验证所有条目,收集有效条目后再一次性写入Core Data。这样不会阻塞主线程,也能保证Feed加载的流畅性。

示例代码:

-(void) saveFeed:(NSArray *)items inContext:(NSManagedObjectContext*)context {
    // 创建串行队列处理验证结果,保证线程安全
    dispatch_queue_t resultQueue = dispatch_queue_create("com.yourapp.feed.validation", DISPATCH_QUEUE_SERIAL);
    NSMutableArray *validItems = [NSMutableArray array];
    
    // 用计数组跟踪所有异步任务完成情况
    __block NSInteger pendingTasks = items.count;
    
    for (id item in items) {
        NSString *link = item.videosurl;
        [link foundValidFileAtSurlWithCompletion:^(BOOL isValid) {
            dispatch_async(resultQueue, ^{
                if (isValid) {
                    [validItems addObject:item];
                }
                
                pendingTasks--;
                if (pendingTasks == 0) {
                    // 所有验证完成,写入Core Data
                    [self saveValidItems:validItems toContext:context];
                }
            });
        }];
    }
}

-(void) saveValidItems:(NSArray *)validItems toContext:(NSManagedObjectContext*)context {
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Videofeed" inManagedObjectContext:context];
    
    for (id item in validItems) {
        NSManagedObject *record = [[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:context];
        [record setValue:item.title forKey:@"title"];
        [record setValue:item.videosurl forKey:@"videosurl"];
    }
    
    NSError *error = nil;
    [context save:&error];
    if (error) {
        NSLog(@"Core Data保存失败:%@", error.localizedDescription);
    }
    
    // 保存完成后,NSFetchedResultsController会自动更新UI
}

方法2:使用Objective-C的Async/Await(iOS13+)

如果你的App支持iOS13及以上,可以利用Objective-C对async/await的支持,直接调用Swift的Function A,让同步代码“等待”异步验证结果(实际是挂起线程,不会阻塞主线程,需在后台队列执行)。

示例代码:

// 需在后台队列调用该方法,避免阻塞主线程
-(void) saveFeedAsync:(NSArray *)items inContext:(NSManagedObjectContext*)context {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSEntityDescription *entity = [NSEntityDescription entityForName:@"Videofeed" inManagedObjectContext:context];
        
        for (id item in items) {
            NSString *link = item.videosurl;
            BOOL isValid = NO;
            NSError *error = nil;
            
            // 调用Swift的async方法,Objective-C中用回调包装
            [link fileExistsAt:@""] completionHandler:^(BOOL result, NSError * _Nullable error) {
                isValid = result;
            }];
            
            if (!isValid || error) {
                continue;
            }
            
            NSManagedObject *record = [[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:context];
            [record setValue:item.title forKey:@"title"];
            [record setValue:item.videosurl forKey:@"videosurl"];
        }
        
        NSError *saveError = nil;
        [context save:&saveError];
        if (saveError) {
            NSLog(@"Core Data保存失败:%@", saveError.localizedDescription);
        }
    });
}

方法3:用信号量阻塞线程(不推荐)

可以用dispatch_semaphore_t让同步代码等待异步结果,但这种方法会阻塞当前线程,如果在主线程调用会导致UI卡顿,仅适合后台队列的场景。

示例代码:

-(void) saveFeed:(NSArray *)items inContext:(NSManagedObjectContext*)context {
    // 必须在后台队列执行,否则会阻塞主线程
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSEntityDescription *entity = [NSEntityDescription entityForName:@"Videofeed" inManagedObjectContext:context];
        
        for (id item in items) {
            NSString *link = item.videosurl;
            __block BOOL validFile = NO;
            dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
            
            [link foundValidFileAtSurlWithCompletion:^(BOOL isValid) {
                validFile = isValid;
                dispatch_semaphore_signal(semaphore);
            }];
            
            // 等待异步任务完成,超时时间设为1秒(和验证请求超时一致)
            dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC));
            
            if (!validFile) {
                continue;
            }
            
            NSManagedObject *record = [[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:context];
            [record setValue:item.title forKey:@"title"];
            [record setValue:item.videosurl forKey:@"videosurl"];
        }
        
        NSError *error = nil;
        [context save:&error];
        if (error) {
            NSLog(@"Core Data保存失败:%@", error.localizedDescription);
        }
    });
}

内容的提问来源于stack exchange,提问作者user6631314

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 16:59:52