Retaining objects in blocks - objective-c

The following is my code using GCD to fetch data from the network and then pass it to a response block on the main queue.
+ (void)allData:(void(^)(NSArray*))responseBlock
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
__block NSArray *data = [[self all] retain]; // get data from network
dispatch_async(dispatch_get_main_queue(), ^{
responseBlock(data); // 2
});
});
}
The [[self all] retain] is to prevent the object from being released . But now the responseBlock has to release it . Is this the correct way ?

you can do like this
+ (void)allData:(void(^)(NSArray*))responseBlock
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
__block NSArray *data = [[self all] retain]; // get data from network
dispatch_async(dispatch_get_main_queue(), ^{
responseBlock(data); // 2
[data release]; // you can release here
data = nil;
});
});
}
or simply remove __block, the inner block (the one you pass to main queue) will retain/release data automatically and because data does not retaining anything else so I can't see any possible retain cycle here (since self here is a Class).

Related

Block code in a method does not get run

I have the code below to load a group of images and notify me via completionHandler when they are all done loading. However, I find that certain dispatch_group_leave won't be called at times and my guess is imageLoader is deallocated before the block gets to run. If I put a reference of imageLoader within the loadImageWithURL:completionHandler block, then everything works as intended.
Is my guess of the cause correct? What's the correct fix for this issue? I know ARC does block copy in most cases automatically, should I do a block copy in this case?
- (void)loadGroupImagesAsyncWithCompletion:(void(^)(NSError *))completionHandler {
dispatch_group_t group = dispatch_group_create();
int index = 0;
for (Item *item in items) {
char queueLabel[30] = {0};
sprintf(queueLabel, "loader%d", index);
dispatch_queue_t queue = dispatch_queue_create(queueLabel, NULL);
dispatch_group_enter(group);
dispatch_async(queue, ^{
ImageLoader *imageLoader = [[ImageLoader alloc] init];
[imageLoader loadImageWithURL:url completionHandler:^(UIImage *image, NSError *error) {
if (image) {
item.image = image;
}
//NOTE: if item object is referenced in this block,
//then there is no missed dispatch_group_leave call.
dispatch_group_leave(group);
}];
});
}
// Non-blocking wait
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// shouldn't take more than 5 secs to load all images
dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)));
dispatch_async(dispatch_get_main_queue(), ^{
completionHandler(nil);
});
});
}
Here's my guess. It's only a guess because you haven't posted the code of anything to do with ImageLoader.
If -loadImageWithURL:completionHandler: operates asynchronously i.e. it uses an async dispatch queue itself then you could be right that it is being deallocated before the load finishes. This is because its lifetime is just the scope of the for block it is declared in.
In fact, there is no reason why that method needs to do stuff asynchronously, because you have already got it in an asynchronous block. Just have a synchronous method and call dispatch_group_leave() after the method finishes.
EDIT
Given that you have no control over ImageLoader and -loadImageWithURL:completionHandler: operates asynchronously without any help from you, you should remove the dispatch_async wrapper around the call. You'll still have the problem of the ImageLoader being deallocated, but that can be avoided by putting each ImageLoader in a array when you create it.
The code would look something like this:
- (void)loadGroupImagesAsyncWithCompletion:(void(^)(NSError *))completionHandler
{
NSMutableArray* loaders = [[NSMutableArray alloc] init];
dispatch_group_t group = dispatch_group_create();
int index = 0;
for (Item *item in items) {
char queueLabel[30] = {0};
sprintf(queueLabel, "loader%d", index);
dispatch_queue_t queue = dispatch_queue_create(queueLabel, NULL);
dispatch_group_enter(group);
ImageLoader *imageLoader = [[ImageLoader alloc] init];
[imageLoader loadImageWithURL:url completionHandler:^(UIImage *image, NSError *error) {
if (image) {
item.image = image;
}
//NOTE: if item object is referenced in this block,
//then there is no missed dispatch_group_leave call.
dispatch_group_leave(group);
}];
[loaders addObject: imageLoader];
}
// Non-blocking wait
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// shouldn't take more than 5 secs to load all images
dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)));
dispatch_async(dispatch_get_main_queue(), ^{
completionHandler(nil);
});
[loaders removeAllObjects];
});
}

Interval between asynchronous data loading and reloading UITableView

I am asynchronously loading data to an array in a getter method of data.
First of all it produces an empty array, so naturally my table loads 0 rows.
When the data is finished loading, I call reloadData to update my table, however, there seems to be ~6 second gap between the data being downloaded and the UITableView showing the data.
Does anyone know any reasons this might happen?
I use the dispatch_async method with a priority of high.
I've even logged each point in the process of loading the data and inserting the data, and it shows this.
Also, if I keep scrolling the table up and down as it's data is being loaded, the table shows its data as soon as it should, rather than have this gap between insertion.
Code:
- (NSMutableDictionary *)tableDictionary {
if (!_tableDictionary) {
_tableDictionary = [NSMutableDictionary dictionary];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
NSString *URLString = #"http://www.urlToData.com/path/to/file.php?foo=bar";
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:URLString]];
NSError *error = nil;
NSDictionary *objectIDs = [NSJSONSerialization JSONObjectWithData:data options:NSJSONWritingPrettyPrinted error:&error];
NSMutableDictionary *objects = [NSMutableDictionary dictionary];
for (NSInteger i = 0; i < objectIDs.allValues.count; i++) {
NSArray *eventIDs = (objectIDs.allValues)[i];
NSString *eventType = (objectIDs.allKeys)[i];
[objects setValue:[myObject initWithIDs:objectIDs] forKey:#"key"];
}
self.tableDictionary = objects;
self.titlesForSectionHeader = objects.allKeys;
NSLog(#"Done");
[self.tableView reloadData];
});
}
return _tableDictionary;
}
Try reloading your tableview's data on the main thread like this:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
//background processing goes here
//This is where you download your data
dispatch_async(dispatch_get_main_queue(), ^{
//update UI here
[self.tableView reloadData];
});
});
As a slight modification of the code by #danielbeard. I would recommend that all code affecting the current object be done on the main queue. This avoids nasty surprises down the road if KVO or a set property updates the UI.
Finally, I wouldn't use DISPATCH_QUEUE_PRIORITY_HIGH.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Stuff 'n stuff
dispatch_async(dispatch_get_main_queue(), ^{
self.tableDictionary = objects;
self.titlesForSectionHeader = objects.allKeys;
NSLog(#"Done");
[self.tableView reloadData];
});
});

MKNetworkKit and GCD dispatch_group_t

I am trying to use MKNetworkKit to fetch an array of links from a web service, then parse each response on a background thread, and use the dispatch_group_t of GCD to wait until all threads are finished processing. Where I'm stuck is I can't figure out why my dispatch_group_notify is not waiting for all threads in the group to complete. Running this code will print:
results count: 0
added into results, count: 1
added into results, count: 2
The dispatch group is not waiting on its threads. I have also tried dispatch_group_wait but that gave me a crash. I don't know if MKNetworkKit's use of NSOperation is conflicting with this issue. Thanks for any help!
- (MKNetworkOperation *)getABunchOfMovies:(NSArray *)movies onCompletion:(CastResponseBlock)completionBlock onError:(MKNKErrorBlock)errorBlock
{
MKNetworkOperation *operation;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
block NSMutableArray *results = [[NSMutableArray alloc] initWithCapacity:[movies count]];
for (NSString *movieTitle in movies) {
operation = [self operationWithPath:REQUEST_URL(API_KEY, [movieTitle urlEncodedString])];
[operation onCompletion:^(MKNetworkOperation *completedOperation) {
dispatch_group_async(group, queue, ^{
NSDictionary *response = [completedOperation responseJSON];
id result = [self processResponse:response withMovieTitle:movieTitle];
#synchronized (results) {
[results addObject:result];
NSLog(#"added into results, count: %d", [results count]);
}
});
}
onError:^(NSError *error) {
errorBlock(error);
}];
[self enqueueOperation:operation];
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(#"results count: %d", [results count]);
// Return array here
completionBlock(results);
});
dispatch_release(group);
return operation;
}
Edit:
I still can't figure out why, but if I change it to use dispatch_group_enter(group); and match it with a dispatch_group_leave(group); at the end of the completion block, it works. Does anyone have any idea why this is happening?
At the moment MKNetworkKit doesn't support queue completion handlers.
You should consider adding operation dependency instead of this hack.
[lastOperation addDependency:op1];
[lastOperation addDependency:op2];
and assume that when "lastOperation" completes, the queue has indeed completed.
Another way is to KVO the "operationCount" keypath on the engine and check if it reaches zero.
MKNetworkEngine has a code block that does this to show and hide the network activity indicator.

EXC_BAD_ACCESS in nested dispatch_async with an NSAutoreleasePool

I have some code which is similar to the following code:
dispatch_queue_t queue = dispatch_queue_create("", 0);
dispatch_queue_t inner_queue = dispatch_queue_create("", 0);
dispatch_async(queue,
^{
NSAutoreleasePool* autoreleasePool = [[NSAutoreleasePool alloc] init];
NSArray* objects = [self.otherObject getObjectsFromSlowQuery];
[objects enumerateObjectsWithUsingBlock:^(id anObject, NSUInteger idx, BOOL *stop)
{
[anObject prepare];
dispatch_async(inner_queue,
^{
InnerObject* innerObject = anObject.innerObject;
[self.helper doSomethingExpensiveWithObject:innerObject];
});
dispatch_sync(self.syncQueue,
^{
[self insertIntoCollection:anObject];
});
}];
dispatch_release(inner_queue);
[autoreleasePool drain];
});
dispatch_release(queue);
Where [anObject.innerObject] is a nonatomic property.
I got a crash report from a user which shows an EXC_BAD_ACCESS in objc_msgSend() when trying to access a property of innerObject within the doSomethingExpensiveWithObject: call.
At first I was thinking that perhaps the autoreleasePool was drained so the innerObject instance was released before returning from doSomethingExpensiveWithObject: but as far as I know anObject should be retained by the inner dispatch_async call which should also keep the innerObject alive.
What am I missing?
Instruments will make quick work of this - run with zombies and review the reference counts when it stops.

Error trying to assigning __block ALAsset from inside assetForURL:resultBlock:

I am trying to create a method that will return me a ALAsset for a given asset url. (I need upload the asset later and want to do it outside the result block with the result.)
+ (ALAsset*) assetForPhoto:(Photo*)photo
{
ALAssetsLibrary* library = [[[ALAssetsLibrary alloc] init] autorelease];
__block ALAsset* assetToReturn = nil;
NSURL* url = [NSURL URLWithString:photo.assetUrl];
NSLog(#"assetForPhoto: %#[", url);
[library assetForURL:url resultBlock:^(ALAsset *asset)
{
NSLog(#"asset: %#", asset);
assetToReturn = asset;
NSLog(#"asset: %# %d", assetToReturn, [assetToReturn retainCount]);
} failureBlock:^(NSError *error)
{
assetToReturn = nil;
}];
NSLog(#"assetForPhoto: %#]", url);
NSLog(#"assetToReturn: %#", assetToReturn); // Invalid access exception coming here.
return assetToReturn;
}
The problem is assetToReturn gives an EXC_BAD_ACCESS.
Is there some problem if I try to assign pointers from inside the block? I saw some examples of blocks but they are always with simple types like integers etc.
A few things:
You must keep the ALAssetsLibrary instance around that created the ALAsset for as long as you use the asset.
You must register an observer for the ALAssetsLibraryChangedNotification, when that is received any ALAssets you have and any other AssetsLibrary objects will need to be refetched as they will no longer be valid. This can happen at any time.
You shouldn't expect the -assetForURL:resultBlock:failureBlock:, or any of the AssetsLibrary methods with a failureBlock: to be synchronous. They may need to prompt the user for access to the library and will not always have their blocks executed immediately. It's better to put actions that need to happen on success in the success block itself.
Only if you absolutely must make this method synchronous in your app (which I'd advise you to not do), you'll need to wait on a semaphore after calling assetForURL:resultBlock:failureBlock: and optionally spin the runloop if you end up blocking the main thread.
The following implementation should satisfy as a synchronous call under all situations, but really, you should try very hard to make your code asynchronous instead.
- (ALAsset *)assetForURL:(NSURL *)url {
__block ALAsset *result = nil;
__block NSError *assetError = nil;
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[[self assetsLibrary] assetForURL:url resultBlock:^(ALAsset *asset) {
result = [asset retain];
dispatch_semaphore_signal(sema);
} failureBlock:^(NSError *error) {
assetError = [error retain];
dispatch_semaphore_signal(sema);
}];
if ([NSThread isMainThread]) {
while (!result && !assetError) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
}
else {
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
}
dispatch_release(sema);
[assetError release];
return [result autorelease];
}
You should retain and autorelease the asset:
// ...
assetToReturn = [asset retain];
// ...
return [assetToReturn autorelease];