使用具有NSOperation的块

时间:2013-03-27 13:57:26

标签: iphone ios objective-c-blocks nsoperation nsoperationqueue

这是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

1 个答案:

答案 0 :(得分:1)

有几点想法:

  1. 您将块定义为weak属性。在使用Objective-C编程指南的Working With Blocks部分中,他们建议您使用copy代替。 (注意,我们正在复制块,而不是它使用的对象;但是你必须要小心那个块中的强引用。)块变量的范围令人惊讶地微妙。 (有关示例,请参阅块编程主题指南中的Patterns to Avoid。)并且调用因block而发布的weak可能会产生一些不可预测的结果。使用copy

  2. 您的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];
        }
    }];
    
  3. 您可能希望对imageNamed方法本身在首次检索图像时速度慢这一事实敏感。如果所有单元格都返回相同的imageNamed,则这不是问题,但是如果(a)使用唯一的imageNamed文件; (b)如果处理较旧的较慢设备,您甚至可能希望将imageNamed进程推送到后台队列中。在较新的设备上(或在模拟器上),您永远不会看到问题,但如果您在旧的慢速设备上进行测试,您可能会在快速的tableview滚动中看到一些卡顿(但因为imageNamed缓存,您只会在首次检索到图片时看到这种口吃的UI ...如果您为所有图片使用相同的图片名称,您可能看不到这一点。)

  4. 您对completBlock的呼叫正在后台队列中完成。由于您正在进行UI更新,因此您可能需要确保将其重新发送回主队列:

    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        // call the completBlock here
    }];