我对 Core Data 比较陌生,正在寻找一些建议。我的项目:我在这里有几个对象: - SMCoreData,主要是围绕 CoreData 项目自动生成的代码 - SMUploader 索引 ALAssetLibrary 中的资产,将结果写入 SMCoreData,并具有上传照片的队列机制。- SMUploaderViewController 一个 UICollectionView 和一些显示队列进度的开关。
这些类在大多数情况下都很好用。当用户启动应用程序时,他们的相机胶卷被编入索引并开始上传。它将成功上传 1000 张照片。
当更多添加到上传队列时,问题会稍后出现。我正在观察 ALAssetLibrary 的变化(比如有人使用 Safari 保存照片)。我的应用程序将收到通知,将图像排队,并在用户翻转开关时尝试上传(与之前相同)。
这是我遇到一个怪癖的地方。我首先从核心数据中获取下一个对象,从 URL(核心数据对象)获取资产,然后将文件复制到我的沙箱中。然后我用文件大小和临时文件路径更新 CoreData。但是,对 [context save] 的调用永远不会返回(但是它之前已经工作了 1000 次)。处理器处于 0%,它只是坐在那里没有返回。起初听起来像是队列问题,但是我使用的是后台队列:
MYCoreData 主要是来自 CoreData 项目的样板代码。我把它移到一个类中来封装它。我确实改变了 managedObjectContext 的创建方式:
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
MYUploader 有两个队列。一种用于索引资产库,一种用于处理上传队列。
MYUploaderViewController 向 MYUploader 注册了一些回调块,以便可以呈现 UI 更新。这些调用总是使用 MYUploader 从主队列中完成。
一小段代码:
-(void)uploadAsset:(SMUploadAsset*)uploadAsset completion:(SMUploaderBoolBlock)completion{
// 1.) Dump asset to a temporary file to operate with
// 1a) Update core data with temporaryFile, size, and md5
// 2.) Create asset on our server
// 2a) Update core data with s3URL
// 3.) Upload file to s3RUL
// 4.) Tell our server that the upload has completed
// 5.) Update core data with uploaded and uploadedDate
// 6.) Delete temporary file
// 7.) Remove the uploadAsset from the queue
NSLog(@"%s", __func__);
if(uploadAsset.image){
NSLog(@"........................................................... Uploading Image ................................................ ");
uploadAsset.uploadQueueState = SMUploadAssetQueueStateInProgress;
// 1.) Dump asset to a temporary file to operate with
NSLog(@"Upload step 1. ");
NSURL *assetURL = [NSURL URLWithString:[NSString stringWithFormat:@"%@", uploadAsset.image.uri]];
NSLog(@"--- UPLOAD 1.) Dumping file");
[SMUploaderImageRipper bytesFromObject:assetURL completion:^(SMUploadProperties *uploadProperties) {
if(uploadProperties == nil){
NSLog(@"Upload step 1 FAILED");
uploadAsset.uploadQueueState = SMUploadAssetQueueStateFailed;
NSLog(@"****** UPLOAD ERROR: Failed to dump asset to temporary file");
completion(NO);
return;
}
NSLog(@"Upload step 1 complete");
// 1a) Update core data with temporaryFile, size, and md5
NSLog(@"Upload step 1a. Updating core data with temp file, size, and md5");
[self updateUploadAsset:uploadAsset uploadProperties:uploadProperties completion:^(BOOL success) {
if(success == NO){
NSLog(@"Upload step 1a FAILED");
uploadAsset.uploadQueueState = SMUploadAssetQueueStateFailed;
NSLog(@"****** UPLOAD ERROR: Failed to add temporaryFile, size, and md5 to CoreData");
completion(NO);
return;
}
NSLog(@"Upload step 1a complete");
....
然后更新 CoreData 对象的方法:
-(void)updateUploadAsset:(SMUploadAsset*)uploadAsset
uploadProperties:(SMUploadProperties*)uploadProperties
completion:(SMUploaderBoolBlock)completion{
SMCoreData *coreData = [SMCoreData sharedInstance];
NSManagedObjectContext *context = [coreData managedObjectContext];
uploadAsset.md5 = uploadProperties.hash;
uploadAsset.temporaryFile = uploadProperties.temporaryFile;
uploadAsset.size = uploadProperties.size;
NSError *cdError;
// THIS CALL NEVER RETURNS
if (![context save:&cdError]) {
NSLog(@"Whoops, couldn't save: %@", [cdError localizedDescription]);
completion(NO);
}
completion(YES);
}
我很高兴发布更多代码或回答问题,因为我很困惑。
更新:在我看来,是 ALAssetLibarary 观察者以某种方式导致了这个问题。这是我为通知所做的事情。我尝试过使用主队列以及我拥有的不同的后台队列,但这始终是让 CoreData 进入时髦状态的原因。如果我将其注释掉,然后如上所述运行应用程序(去 safari,保存图像,返回),但我没有收到此通知,而是按下一个调用相同代码的按钮(enqueueCameraRollWithCompletion),一切都很好。
这里有什么解决方案?我真的很想保留这个功能。
[[NSNotificationCenter defaultCenter] addObserverForName:ALAssetsLibraryChangedNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { dispatch_async(self.processingQueue, ^{ [self enqueueCameraRollWithCompletion:^(BOOL success) { }]; }); }];
编辑:使用 performBlockAndWait 请求代码更新
-(void)updateUploadAsset:(SMUploadAsset*)uploadAsset
uploadProperties:(SMUploadProperties*)uploadProperties
completion:(SMUploaderBoolBlock)completion{
SMCoreData *coreData = [SMCoreData sharedInstance];
NSManagedObjectContext *context = [coreData managedObjectContext];
[context performBlockAndWait:^{
uploadAsset.md5 = uploadProperties.hash;
uploadAsset.temporaryFile = uploadProperties.temporaryFile;
uploadAsset.size = uploadProperties.size;
if ([coreData saveContext]){
completion(YES);
}
else{
completion(NO);
}
}];
}