当我在iOs应用程序中更改视图控制器时,我发生了这个非常奇怪的错误。 首先是一些背景信息。
我正在后台NSOperation
从网络上检索图片。此操作从在集合视图单元格中实例化的另一个(后台)操作开始。这种方法的工作方式是单元格创建一个对象,然后将自己设置为该对象的观察者,然后创建一个NSOperation
,并将该对象作为参数。第一级操作将启动第二个操作,该操作将从Web获取图像,另一个NSOperation
将尝试从文件中获取数据(如果可能),并通过委派将其报告给第一个操作。第一级操作将改变观察对象的属性,从而触发KVO。 collection / tableView单元格将从- observeValueChange
方法更新。
问题在于:
有时单元格会消失(重用或取消分配),当后台线程尝试在观察对象上设置值时,它会触发EXC_BREAKPOINT
异常([collectionViewCell message retain sent to deallocated instance])
。
为了防止这种情况,我尝试在单元格上实现-prepareForReuse和-dealloc。但错误不断发生。 流程看起来像这样:
- 用户VC
collectionViewWithCells
-cell创建对象和NSOperation
1
NSoperation 1创建了NSOperation2(这是从web获取的两种类型或从文件中获取)
NSOpeartion 2从互联网或本地文件获取图像
NSoperation 2将数据发送到NSOperation1
用户已离开此屏幕
NSOperation 1尝试设置观察对象的数据
- CRASH
这是单元格内的代码:
@interface CustomCollectionViewCell ()
@property (strong, nonatomic) NSOperationQueue *imagesOperationQueue;
@property (strong, nonatomic) ImageObject *imgObj;
@end
@implementation CustomCollectionViewCell
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
- (void)prepareForReuse{
[self clearDelegatesAndObservers];
[super prepareForReuse];
}
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
// Drawing code
}
*/
- (void) getImage {
self.imgObj = [ImageObject newRefrenceWithId:obj_ref];
[self.imgObj addObserver:self forKeyPath:@"data" options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld) context:NULL];
TaskImageReqCache *imgReq = [[TaskImageReqCache alloc] initWithUrl:imgUrl andImageObject:self.imgObj];
[self.imagesOperationQueue addOperation:imgReq];
}
#pragma mark - KVO
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if (object == self.imgObj) {
UIImage *img = [UIImage imageWithData:self.imgObj.data];
self.thumbnailImage.image = img;
}
}
- (void)dealloc
{
[self clearDelegatesAndObservers];
}
- (void)clearDelegatesAndObservers
{
[self.imagesOperationQueue cancelAllOperations];
self.thumbnailImage.image = nil;
[self.imgObj removeObserver:self forKeyPath:@"data"];
[self.pageListAdapter removeDelegateAtIndex:self.myIndexInCollection];
self.imgObj = nil;
}
在第一级NSOperation中,这是异常断点显示发生崩溃的地方:
- (void)didLoadDataFromFile:(NSData *)data
{
if (self.isCancelled) {
[self.opQueue cancelAllOperations];
[self completeOperation];
return;
}
if (!fileDownloadedFromWeb) {
self.observedObject.data = data; // CRASH
}
dataFromDisk = data;
fileReadDone = YES;
if (debugLog) {
NSLog(@"image loaded from local cache (%@)",self.sUrl);
}
}
有关如何防止此次崩溃的任何建议吗? 谢谢。
编辑添加: 我想要实现的是:当显示一个tableView单元格时,会激活一个nsoperation来从网络中获取图像。如果用户快速滚动并且操作尚未完成,我需要结束它并取消分配任何数据,并且当重复使用该单元时,启动一个新操作以从互联网获取适当的图像...
答案 0 :(得分:0)
这需要很长时间才能成为评论,所以我会给它一个答案。
崩溃的原因与UICollectionViewCell
被回收和解除分配的事实有关。 ARC已将[cvcell retain]
置于错误的位置。所以,有几个选择:
NSOperation
创建UICollectionViewCell
。UICollectionViewController
/ UICollectionView
,以便留在内存中。UICollectionViewController
/ UICollectionView
的属性/指针,以便即使用户离开它也会保留在内存中。 (请确保将其保留为strong
或retain
)。注意:所有这些解决方案都做同样的事情,强制ARC将保留调用放在其他地方或完全删除它。
答案 1 :(得分:0)
在没有您控制的情况下,单元格会被频繁重用和重新分配,因此您应该避免将未决请求或操作分配给它们。
而是处理集合视图数据源(视图控制器)中的操作,并跟踪每个单元格但不是字典中indexPath
的操作。
更好地保持这是一种良好的体验,并使用可信和经过测试的内容,例如SDwebImage。
答案 2 :(得分:0)
根据以下评论,我们知道:
- (void)didLoadDataFromFile:(NSData *)data
在另一个线程上调用dealloc,因此存在竞争条件。您需要在与其解除分配的线程相同的线程上访问self.observedObject。我假设"观察对象"是弱参考?
dispatch_sync(dispatch_get_main_queue(), ^{
if (!fileDownloadedFromWeb) {
// Get a strong reference. This will retain observedObject - we must do this
// on the same thread as observedObject:dealloc is called, to prevent retaining
// an object during (or after) dealloc.
ObservedObject *strongRef = self.observedObject;
// This will do nothing if strongRef is nil.
strongRef.data = data;
}
});
更结构化的方法是让单元格从单例缓存中获取其所有图像(看起来好像目前没有缓存)。该单元显然需要将自己注册为缓存中特定URL的观察者,并且缓存将在URL下载时通知单元。缓存应该在主线程上发布该通知。
缓存本身将管理所有下载,并且不存在背景释放问题,因为它将是单例。
如果您不想缓存,那很好。使用相同的体系结构,但是将缓存称为图像提取器。如果您愿意,可以随后添加缓存。
编辑 - 如果你的对象可以被重用,而不是像UITableViewCells那样被解除分配,那么单元格需要小心忽略与先前获取相关的图像的通知。这些模型中的任何一个都可以使用
a)单元格保留对NSOperation的引用,直到NSOperation将其调回,或者直到调用prepareForReuse为止。来自无法识别的NSOperation的任何回调必须是先前的提取(我们试图取消),并且应该被忽略。我不是真的推荐这个模型,让细胞了解操作,反之亦然。
b)NSOperation在完成时(在主线程上)发送通知,并在用户信息中指定所请求的url /路径。 UITableViewCell会记住它尝试获取的URL /路径,并忽略与其他图像相关的通知。它在dealloc / prepareForReuse中未注意到该路径。