Reputation: 676
This is a continuation of this post.
I have a problem with blocks with an NSOperation, my application crash in the completionblock
.
i think that the problem is a retain cycle (i have a warning : Capturing self strongly in this block is likely to lead to a retain cycle
), i try this solution (see the code above), the warning is gone but the app still crash.
the application is a simple tableView. the operation apply some modification in the passed image.
this is the first time i create and use block myself, thank you to give me some fundamental explication
code:
TableViewController.m:
in 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
Upvotes: 1
Views: 1576
Reputation: 438417
A couple of thoughts:
You have your block defined as a weak
property. In the Working With Blocks section of Programming with Objective-C guide they advise that you use copy
instead. (Note, we're copying the block, not the objects it uses; you do have to be careful of strong references within that block, though.) The scope of block variables is surprisingly subtle. (See Patterns to Avoid in the Block Programming Topics guide for examples.) And calling a block
that was released because it was weak
can generate some unpredictable results. Use copy
.
Your tableview has a subtle issue, which is what would happen if the cell had scrolled off by the time the completion block was invoked. The cell could have been reused for another row of your table. (As it turns out, corner rounding logic is probably fast enough that this problem is unlikely to manifest itself, but this is critical if you do slower operations such as downloading images or if the images were huge.) Anyway, instead of:
[[ImageManager sharedManager] modifyImage:[UIImage imageNamed:@"anImage.png"] completionBlock:^(UIImage *image, NSError *error) {
[cell.imageView setImage:image];
}];
you might want to call the UITableView
method cellForRowAtIndexPath
(not to be confused with the UITableViewController
method 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];
}
}];
You might want to be sensitive to the fact that the imageNamed
method, itself, can be slow with first retrieving an image. If all of the cells are returning the same imageNamed
, this is not an issue, but if (a) using unique imageNamed
files; and (b) if dealing with older, slower device, you might even want to push the imageNamed
process into the background queue, too. On newer devices (or on the simulator), you'll never see the issue, but if you test on a old, slow device, you might see some stuttering in a fast tableview scroll (but because imageNamed
caches, you'll only see this stuttering UI when image is first retrieved...if you're using the same image name for all of them, you might not see this.)
Your call to completBlock
is being done in the background queue. Since you're doing UI updates, you might want to make sure to dispatch this back to the main queue:
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// call the completBlock here
}];
Upvotes: 1