如何在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
相关产品推荐
相关产品推荐

