这是this post的延续。
我对使用NSOperation的块有问题,我的应用程序崩溃在completionblock
。
我认为问题是保留周期(我有一个警告:Capturing self strongly in this block is likely to lead to a retain cycle
),我尝试this solution(参见上面的代码),警告消失但应用程序仍然崩溃。< / p>
应用程序是一个简单的tableView。该操作在传递的图像中应用了一些修改。
这是我第一次自己创建和使用块,谢谢你给我一些基本的解释
代码:的
TableViewController.m:
tableView中的:cellForRowAtIndexPath:
[[ImageManager sharedManager] modifyImage:[UIImage imageNamed:@"anImage.png"] completionBlock:^(UIImage *image, NSError *error) {
[cell.imageView setImage:image];
}];
ImageManager.h:
#import <Foundation/Foundation.h>
@interface ImageManager : NSObject
@property(nonatomic, strong) NSOperationQueue *imageOperationQueu;
-(void) modifyImage:(UIImage*)image completionBlock:(void(^)(UIImage *image,NSError *error)) completBlock;
+ (id) sharedManager;
@end
ImageManager.m:
#import "ImageManager.h"
#import "ImageOperations.h"
static ImageManager *MySharedManager = nil;
@implementation ImageManager
@synthesize imageOperationQueu;
+ (id)sharedManager
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if (MySharedManager == nil) MySharedManager = [[self alloc] init];
});
return MySharedManager;
}
- (NSOperationQueue *)imageOperationQueu
{
if (!imageOperationQueu)
{
imageOperationQueu = [[NSOperationQueue alloc] init];
[imageOperationQueu setMaxConcurrentOperationCount:3];
imageOperationQueu.name = @"imageOperationQueu";
}
return imageOperationQueu;
}
-(void) modifyImage:(UIImage*)image completionBlock:(void(^)(UIImage *image,NSError *error)) completBlock
{
ImageOperations *op = [[ImageOperations alloc]initWithImage:image WithCompletionBlock:completBlock];
[self.imageOperationQueu addOperation:op];
}
@end
ImageOperations.h:
#import <Foundation/Foundation.h>
typedef void (^CompletionBlock)(UIImage *image,NSError *error);
@interface ImageOperations : NSOperation
@property(nonatomic, weak) CompletionBlock completBlock;
@property(nonatomic, strong) UIImage *imageToTransform;
-(id)initWithImage:(UIImage *)image WithCompletionBlock:(CompletionBlock) block;
@end
ImageOperations.m:
#import "ImageOperations.h"
@implementation ImageOperations
@synthesize imageToTransform;
@synthesize completBlock;
-(id)initWithImage:(UIImage *)image WithCompletionBlock:(CompletionBlock) block
{
if (self = [super init])
{
NSLog(@"initWithImage");
self.imageToTransform = image;
[self setCompletBlock:block];
}
return self;
}
- (void)main
{
@autoreleasepool
{
UIImage *img = [self setRoundedImage:self.imageToTransform];
__weak ImageOperations *imgOp = self;
[imgOp setCompletionBlock:^{
NSLog(@"setCompletionBlock");
imgOp.completBlock(img,nil);
}];
}
}
-(UIImage*)setRoundedImage:(UIImage*)image
{
// ..
}
@end
答案 0 :(得分:1)
有几点想法:
您将块定义为weak
属性。在使用Objective-C编程指南的Working With Blocks部分中,他们建议您使用copy
代替。 (注意,我们正在复制块,而不是它使用的对象;但是你必须要小心那个块中的强引用。)块变量的范围令人惊讶地微妙。 (有关示例,请参阅块编程主题指南中的Patterns to Avoid。)并且调用因block
而发布的weak
可能会产生一些不可预测的结果。使用copy
。
您的tableview有一个微妙的问题,如果在调用完成块时单元格已滚动,将会发生这种情况。该单元格可以重复用于表格的另一行。 (事实证明,角落舍入逻辑可能足够快,以至于这个问题不太可能表现出来,但如果您执行较慢的操作(如下载图像或图像很大),这一点至关重要。)无论如何,而不是:
[[ImageManager sharedManager] modifyImage:[UIImage imageNamed:@"anImage.png"] completionBlock:^(UIImage *image, NSError *error) {
[cell.imageView setImage:image];
}];
您可能想要调用UITableView
方法cellForRowAtIndexPath
(不要与UITableViewController
方法tableView:cellForRowAtIndexPath:
混淆):
[[ImageManager sharedManager] modifyImage:[UIImage imageNamed:@"anImage.png"] completionBlock:^(UIImage *image, NSError *error) {
if (error == nil)
{
UITableViewCell *updateCell = [tableView cellForRowAtIndexPath:indexPath];
// if cell has scrolled off screen, this will be `nil`, so let's check
if (updateCell)
[updateCell.imageView setImage:image];
}
}];
您可能希望对imageNamed
方法本身在首次检索图像时速度慢这一事实敏感。如果所有单元格都返回相同的imageNamed
,则这不是问题,但是如果(a)使用唯一的imageNamed
文件; (b)如果处理较旧的较慢设备,您甚至可能希望将imageNamed
进程推送到后台队列中。在较新的设备上(或在模拟器上),您永远不会看到问题,但如果您在旧的慢速设备上进行测试,您可能会在快速的tableview滚动中看到一些卡顿(但因为imageNamed
缓存,您只会在首次检索到图片时看到这种口吃的UI ...如果您为所有图片使用相同的图片名称,您可能看不到这一点。)
您对completBlock
的呼叫正在后台队列中完成。由于您正在进行UI更新,因此您可能需要确保将其重新发送回主队列:
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// call the completBlock here
}];