I am new to blocks and am trying to figure out how to wait for the block to finish before performing my action (in this case a nslog) So how can I wait till the block is done before performing this nslog in the code below: NSLog(#"convertedPhotos::%#",convertedImages);
convertedImages = [[NSMutableArray alloc] init];
for (NSDictionary *photo in photos) {
// photo is a dictionary containing a "caption" and a "urlRep"
[photoUrls addObject:photo[#"urlRep"]];
}
if (photoUrls.count) {
for (id photos in photoUrls){
NSString *urlString = photos;
[self base64ImageAtUrlString:urlString result:^(NSString *base64) {
[jsonWithPhotos setObject:convertedImages forKey:#"photo64"];
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:jsonWithPhotos
options:NSJSONWritingPrettyPrinted // Pass 0 if you don't care about the readability of the generated string
error:&error];
if (! jsonData) {
NSLog(#"Got an error: %#", error);
} else {
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
NSLog(#"json::%#",jsonString);
}
}];
}
}
else {
NSLog(#"where are my urls?");
}
NSLog(#"convertedPhotos::%#",convertedImages);
}
}
this method/block is called from above
- (void)base64ImageAtUrlString:(NSString *)urlString result:(void (^)(NSString *))completion {
NSURL *url = [NSURL URLWithString:urlString];
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
[library assetForURL:url resultBlock:^(ALAsset *asset) {
// borrowing your code, here... didn't check it....
ALAssetRepresentation *representation = [asset defaultRepresentation];
CGImageRef imageRef = [representation fullResolutionImage];
//TODO: Deal with JPG or PNG
NSData *imageData = UIImageJPEGRepresentation([UIImage imageWithCGImage:imageRef], 0.1);
NSString *base64 = [imageData base64EncodedString];
completion(base64);
[convertedImages addObject:base64];
// NSLog(#"converted::%#",convertedImages);
} failureBlock:^(NSError *error) {
NSLog(#"that didn't work %#", error);
}];
}
I would use NSOperationQueue (or dispatch queue) and NSCondition (or dispatch group) to wait for operations to complete. It is also important to wrap blocks in #autoreleasepool to flush memory once you do not need it if you work with memory consuming objects like NSData.
example:
// create results array
__block NSMutableArray* results = [NSMutableArray new];
// create serial queue
dispatch_queue_t queue = dispatch_queue_create("myQueue", 0);
for(NSInteger i = 0; i < 10; i++) {
// enqueue operation in queue
dispatch_async(queue, ^{
// create semaphore
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
// do something async, I do use another dispatch_queue for example
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
// wrap in autoreleasepool to release memory upon completion
// in your case wrap the resultBlock in autoreleasepool
#autoreleasepool {
// here for example the nested operation sleeps for two seconds
sleep(2);
// add the operation result to array
// I construct an array of strings for example
[results addObject:[NSString stringWithFormat:#"Operation %d has finished.", i]];
// signal that nested async operation completed
// to wake up dispatch_semaphore_wait below
dispatch_semaphore_signal(sema);
}
});
// wait until the nested async operation signals that its finished
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
NSLog(#"Finished single operation.");
});
}
// will be called once all operations complete
dispatch_async(queue, ^{
NSLog(#"Finished all jobs.");
NSLog(#"Results: %#", results);
});
For any non-main queue use semaphores
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// some serious stuff here
...
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
In case you want to wait for async task execution being in the main queue - you wouldn't probably want to block it while waiting for a semaphore.
I use this construction which doesn't freeze the UI for the main queue only.
__block BOOL flag = NO;
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// some serious stuff here
...
flag = YES;
});
// Run until 'flag' is not flagged (wait for the completion block to finish executing
while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true) && !flag){};
Your best option is not to use blocks directly. Instead, create instances of NSBlockOperation and add them to an operation queue. Then create one more NSBlockOperation and make it dependent upon all of the other operations. This ensures that the last operation is only run after all others are completed and allows you to control how many operations will run at any one time.
If you have nested block calls, or some API that you can't change to enable this then you can still do it if you create an NSOperation subclass such that the operation does not complete until all of the asynchronous operations are complete. Take a look at dribin.org concurrent operations.
Related
I have a content that consists of main file and additional files.
So here is the problem: At first I need to download,unpack and insert into database additional files and only then do the same thing for main file. Additional files are needed to be downloaded serial, and main file must be downloaded after them.
What is the right way to do it?
Right now I'm doing it this way:
- (void)checkIfPlacesAreDownloaded:(NSArray *)places{
[SVProgressHUD showWithStatus:#"Downloading places"];
dispatch_group_t group = dispatch_group_create();
for(NSDictionary *place in places){
BOOL result = [IDGDatabaseManager checkIfPlaceIsDownloaded:place];
if(!result){
dispatch_group_enter(group);
[self downloadPlace:place withCompletionHandler:^{
[IDGDatabaseManager setPlaceDownloaded:[place objectForKey:#"place_ID"]
WithCompletionBlock:^(BOOL success, NSError *error) {
dispatch_group_leave(group);
}];
}];
}
}
dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self downloadExcursionWithParams:self.excursionDownloadResponse];
});
}
It only works if there is one file in "places" array. If there is more than one file they start to be downloaded parallel and it is not suitable for me.
I think that downloadPlace:withCompletionHandler: method works asynchronously on the background concurrent queue. That is why the file downloads run in parallel. I'd use a private serial queue instead or simply do the next:
[SVProgressHUD showWithStatus:#"Downloading places"];
dispatch_group_t group = dispatch_group_create();
// create a serial background queue to run the file downloads
dispatch_queue_attr_t qosAttribute = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_UTILITY, 0);
dispatch_queue_t myQueue = dispatch_queue_create("com.YourApp.YourQueue", qosAttribute);
for(NSDictionary *place in places){
BOOL result = [IDGDatabaseManager checkIfPlaceIsDownloaded:place];
if(!result){
dispatch_group_enter(group);
// run the download async on the serial bg queue
__weak __typeof(self) weakSelf = self;
dispatch_async(myQueue, ^{
__typeof(self) strongSelf = self;
// we need a semaphore to wait for the download completion
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[strongSelf downloadPlace:place withCompletionHandler:^{
[IDGDatabaseManager setPlaceDownloaded:[place objectForKey:#"place_ID"]
WithCompletionBlock:^(BOOL success, NSError *error) {
dispatch_semaphore_signal(sema);
}];
}];
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
dispatch_group_leave(group);
});
}
}
dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[self downloadExcursionWithParams:self.excursionDownloadResponse];
});
I am using generateCGImagesAsynchronouslyForTimes to make some images and save them to a NSMutableArray, now when the function generateCGImagesAsynchronouslyForTimes finishes I want to use the image in this array, how can I have the code I want to exectue after all the images have been generated to finish. I would just put it in the completionHandler code block, but I don't want it run multiple times I just want to run it once, after this method has finished.
EDIT
This is all inside - (BFTask *)createImage:(NSInteger)someParameter {
AVAssetImageGenerator *imageGenerator = [AVAssetImageGenerator assetImageGeneratorWithAsset:passsedAsset];
[imageGenerator generateCGImagesAsynchronouslyForTimes:times
completionHandler:^(CMTime requestedTime, CGImageRef image, CMTime actualTime,
AVAssetImageGeneratorResult result, NSError *error) {
if (result == AVAssetImageGeneratorSucceeded) {
UIImage *img = [UIImage imageWithCGImage:image];
NSData *imgData = UIImageJPEGRepresentation(img, 1.0);
UIImage *saveImage = [[UIImage alloc] initWithData:imgData];
[mutaleArray addObject:saveImage];
//I get Assigment to read only property error on line below
completionSource.task = saveImage;
}
]};
What should i be assigning that to?
The two approaches I would consider first are NSOperationQueue (you can detect when it's empty) or the easier choice of using the Bolts framework.
Bolts allows you to create an array of tasks that all run asynchronously and then once they're finished it goes on to the next bit.
Let me get a link...
Here you go... https://github.com/BoltsFramework
You can also get this through cocoapods which makes everything much easier.
An example of how bolts works...
At the moment you will have a function that creates an image asynchronously. Something like... - (UIImage *)createImage: (id)someParameter; well now you can do this...
- (BFTask *)createImage:(NSInteger)someParameter
{
BFTaskCompletionSource *completionSource = [BFTaskCompletionSource taskCompletionSource];
//create your image asynchronously and then set the result of the task
someAsyncMethodToCreateYourImageWithACompletionBlock...^(UIImage *createdImage){
// add the images here...
[self.imageArray addObject:createdImage];
// the result doesn't need to be the image it just informs
// that this one task is complete.
completionSource.result = createdImage;
}
return completionSource.task;
}
Now you have to run the tasks in parallel...
- (void)createAllTheImagesAsyncAndThenDoSomething
{
// create the empty image array here
self.imageArray = [NSMutableArray array];
NSMutableArray *tasks = [NSMutableArray array];
for (NSInteger i=0 ; i<100 ; ++i) {
// Start this creation immediately and add its task to the list.
[tasks addObject:[self createImage:i]];
}
// Return a new task that will be marked as completed when all of the created images are finished.
[[BFTask taskForCompletionOfAllTasks:tasks] continueWithBlock:^id(BFTask *task){
// this code will only run once all the images are created.
// in here self.imageArray is populated with all the images.
}
}
Assuming that generateCGImagesAsynchronouslyForTimes:completionHandler: calls its completion handlers sequentially (that seems reasonable, but the docs don't explicitly promise), then this is very simple. Just set a __block variable to the count of your times and decrement it once per completion. When it's zero, call your other function.
__block NSInteger count = [times count];
[imageGenerator generateCGImagesAsynchronouslyForTimes:times
completionHandler:^(CMTime requestedTime, CGImageRef image, CMTime actualTime,
AVAssetImageGeneratorResult result, NSError *error) {
... Do all the stuff ...
if (--count <= 0) {
finalize()
}
If generateCGImagesAsynchronouslyForTimes: actually does work in parallel and so might call the completion handlers in parallel, then you can handle all of this with dispatch groups.
dispatch_group_t group = dispatch_group_create();
//
// Enter the group once for each time
//
[times enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
dispatch_group_enter(group);
}];
//
// This local variable will be captured, so you don't need a property for it.
//
NSMutableArray *results = [NSMutableArray new];
//
// Register a block to fire when it's all done
//
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(#"Whatever you want to do when everything is done.");
NSLog(#"results is captured by this: %#", results);
});
AVAssetImageGenerator *imageGenerator = [AVAssetImageGenerator assetImageGeneratorWithAsset:nil];
[imageGenerator generateCGImagesAsynchronouslyForTimes:times
completionHandler:^(CMTime requestedTime, CGImageRef image, CMTime actualTime,
AVAssetImageGeneratorResult result, NSError *error)
{
if (result == AVAssetImageGeneratorSucceeded) {
//
// Create saveImage
//
id saveImage = #"";
//
// Update external things on a serial queue.
// You may use your own serial queue if you like.
//
dispatch_sync(dispatch_get_main_queue(), ^{
[results addObject:saveImage];
});
//
// Signal we're done
//
dispatch_group_leave(group);
}
}];
I follow RAY WENDERLICH GCD tutorial- part 2, and I don't get this:
First implementation
- (void)downloadPhotosWithCompletionBlock:(BatchPhotoDownloadingCompletionBlock)completionBlock
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ // 1
__block NSError *error;
dispatch_group_t downloadGroup = dispatch_group_create(); // 2
for (NSInteger i = 0; i < 3; i++) {
NSURL *url;
switch (i) {
case 0:
url = [NSURL URLWithString:kOverlyAttachedGirlfriendURLString];
break;
case 1:
url = [NSURL URLWithString:kSuccessKidURLString];
break;
case 2:
url = [NSURL URLWithString:kLotsOfFacesURLString];
break;
default:
break;
}
dispatch_group_enter(downloadGroup); // 3
Photo *photo = [[Photo alloc] initwithURL:url
withCompletionBlock:^(UIImage *image, NSError *_error) {
if (_error) {
error = _error;
}
dispatch_group_leave(downloadGroup); // 4
}];
[[PhotoManager sharedManager] addPhoto:photo];
}
dispatch_group_wait(downloadGroup, DISPATCH_TIME_FOREVER); // 5
dispatch_async(dispatch_get_main_queue(), ^{ // 6
if (completionBlock) { // 7
completionBlock(error);
}
});
});
}
Second Implementation:
- (void)downloadPhotosWithCompletionBlock:(BatchPhotoDownloadingCompletionBlock)completionBlock
{
// 1
__block NSError *error;
dispatch_group_t downloadGroup = dispatch_group_create();
for (NSInteger i = 0; i < 3; i++) {
NSURL *url;
switch (i) {
case 0:
url = [NSURL URLWithString:kOverlyAttachedGirlfriendURLString];
break;
case 1:
url = [NSURL URLWithString:kSuccessKidURLString];
break;
case 2:
url = [NSURL URLWithString:kLotsOfFacesURLString];
break;
default:
break;
}
dispatch_group_enter(downloadGroup); // 2
Photo *photo = [[Photo alloc] initwithURL:url
withCompletionBlock:^(UIImage *image, NSError *_error) {
if (_error) {
error = _error;
}
dispatch_group_leave(downloadGroup); // 3
}];
[[PhotoManager sharedManager] addPhoto:photo];
}
dispatch_group_notify(downloadGroup, dispatch_get_main_queue(), ^{ // 4
if (completionBlock) {
completionBlock(error);
}
});
}
I the first implementation the relevant code is surrounded with dispatch_async and everything is super clear.
BUT, the second implementation is unclear! I don't get it, how the GCD mechanism is taking any part beside of the notification for enter and leave?
The first implementation runs a background thread starting at
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ // 1
So the whole function runs there. But this background thread gets eventually blocked at this line (it waits for all the photos to be downloaded):
dispatch_group_wait(downloadGroup, DISPATCH_TIME_FOREVER);
This is all fine since you are not blocking the main thread. But it is a bit of a clumsy implementation since,
you block a thread where you really have not to and
the code looks a bit ugly with the dispatch_async around the whole function.
So the second implementation has two benefits:
It's not that "ugly" and a bit more readable (because you get rid of the dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{})
and there is no block, because dispatch_group_notify serves as the asynchronous completion block.
The first method is an asynchronous method. You can call it from any thread, including the main thread. It will perform actions in a background thread, and when it's done, it will dispatch the callback block on the main thread. A call to this method returns immediately, the callback will be called a lot later.
The second method is a blocking method. The method will not finish until the photos are downloaded, and then it calls your callback method. This method should most definitely not be called from the main thread, only from a background thread.
I'm trying to update NSManagedObject's in the background based on properties from a network fetch. Having trouble wrapping my head around concurrency. What I have tried is
fetch NSManagedObjects(Asset) on a localcontext (NSPrivateQueueType)
enumerate over assets array perform a network GET request for each asset and add it to a dispatch_group
Within the completion block from the network call, map updated values to the asset (This is my problem)
My code looks something like this
__block dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
__block dispatch_group_t taskGroup = dispatch_group_create();
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
NSArray *assets = [Asset MR_findAllInContext:localContext];
for (Asset *asset in assets) {
dispatch_group_enter(taskGroup);
dispatch_async(queue, ^{
[stockClient GET:#"/v1/public/yql" parameters:asset.assetSymbol success:^(NSURLSessionDataTask *task, id responseObject) {
//TODO:Check response
NSDictionary *stockData = [[[responseObject objectForKey:#"query"] objectForKey:#"results"] objectForKey:#"quote"];
[asset mapPropertiesFrom:stockData];//----------> How do I access the localcontext queue?
dispatch_group_leave(taskGroup);
} failure:^(NSURLSessionDataTask *task, NSError *error) {
//TODO:Error handling here
dispatch_group_leave(taskGroup);
}];
});
}
dispatch_group_notify(taskGroup, dispatch_get_main_queue(), ^{
NSLog(#"All Tasks are completed");
});
}];
}
When I try to update the asset(NSManagedObject) from the network completion block
[asset mapPropertiesFrom:stockData];
I receive
CoreData could not fulfill a fault for
I suspect this is because the completion block is on the main queue and my asset was fetched on a private queue and you can't access NSManagedObjects from different queues...but I'm not sure is this is the problem and if it is how to resolve it.
So,how could I complete such a task? Am I going about this completely wrong?
EDIT
Made some changes based on comments below, but I'm receiving error
//Setup Dispatch Group
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t taskGroup = dispatch_group_create();
NSArray *assets = [Asset MR_findAll];
for (Asset *asset in assets) {
dispatch_group_enter(taskGroup);
dispatch_async(queue, ^{
NSManagedObjectContext *localContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
localContext.parentContext = [NSManagedObjectContext MR_defaultContext];
Asset *localAsset = [asset MR_inContext:localContext];
[stockClient updateStockDataFor:localAsset completionHandler:^(NSDictionary *stockData, NSError *error) {
NSManagedObjectContext *responseContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSConfinementConcurrencyType];
responseContext.parentContext = [NSManagedObjectContext MR_defaultContext];
Asset *responseAsset = [localAsset MR_inContext:responseContext];
[responseAsset mapPropertiesFrom:stockData];
[responseContext MR_saveToPersistentStoreAndWait];
dispatch_group_leave(taskGroup);
}];
});
}
dispatch_group_notify(taskGroup, dispatch_get_main_queue(), ^{
NSLog(#"Tasks are Completed");
});
}
Error:
Can only use -performBlock: on an NSManagedObjectContext that was
created with a queue.
Wrap the calls that will read/write anything from core data in a perform block, using:
- (void)performBlock:(void (^)())block
From the docs:
You use this method to send messages to managed objects if the context
was initialized using NSPrivateQueueConcurrencyType or
NSMainQueueConcurrencyType.
This method encapsulates an autorelease pool and a call to
processPendingChanges.
As long as you use this method for your calls there shouldn't be any problems.
I don't understand the second part of your question but in my app im performing updates/reads from network calls all the time using this method. Alternatively you can just dispatch to the main queue just the core data parts. That is, do the network thing on the background and when you need core data use a dispatch queue to the main queue.
As far as MagicalRecord is concerned here, you are dispatching a background task in the background. Basically, your queues are out of sync. I suggest you use a single background queue to save your data.
Try something like this:
__block dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
__block dispatch_group_t taskGroup = dispatch_group_create();
NSArray *assets = [Asset MR_findAll];
for (Asset *asset in assets) {
dispatch_group_enter(taskGroup);
dispatch_async(queue, ^{
NSManagedObjectContext = //create a confinement context
Asset *localAsset = [asset MR_inContext:localContext];
[stockClient GET:#"/v1/public/yql" parameters:asset.assetSymbol success:^(NSURLSessionDataTask *task, id responseObject) {
//TODO:Check response
NSManagedObjectContext *responseContext = //create a confinement context
Asset *responseAsset = [localAsset MR_inContext:responseContext];
NSDictionary *stockData = [[[responseObject objectForKey:#"query"] objectForKey:#"results"] objectForKey:#"quote"];
[asset mapPropertiesFrom:stockData];//----------> How do I access the localcontext queue?
[responseContext MR_saveToPersistentStoreAndWait];
dispatch_group_leave(taskGroup);
} failure:^(NSURLSessionDataTask *task, NSError *error) {
//TODO:Error handling here
dispatch_group_leave(taskGroup);
}];
});
}
dispatch_group_notify(taskGroup, dispatch_get_main_queue(), ^{
NSLog(#"All Tasks are completed");
});
}
I have 3 blocks of code that must execute one by one after previous finished. My implementation not works. I need some help to do it. My code bellow.
for (NSString *i in items)
{
[[RequestAPI sharedInstance]downloadImage:i completion:^(AFHTTPRequestOperation *operation, UIImage *image, NSError *error) {
//here main thread I receive images and go to BG
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
// here I save image on disk and get path
NSString *path = [ImageManager saveImageToDisk:image toEntity:entity withparams:#{#"save" : #"lala"}];
__block NSMutableDictionary *attachments = [NSMutableDictionary dictionary];
__block NSMutableArray *photoPaths = [NSMutableArray array];
dispatch_async(dispatch_get_main_queue(), ^{
//block1. here I load entity and dictionary from it with NSKeyedUnarchiver from CD and set to it image path
if (entity.attachments)
{
attachments = [NSKeyedUnarchiver unarchiveObjectWithData:entity.attachments];
if (attachments[type])
{
photoPaths = attachments[type];
}
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
//block2. here I check all images equality ti themselves in entity
BOOL haveDublicate = NO;
NSData *i = [ImageManager imageDataFromPath:path];
NSArray *photoImages = [ImageManager imageDatasFromPaths:photoPaths];
for (NSData *saved in photoImages)
{
if ([saved isEqualToData: i])
{
haveDublicate = YES;
}
}
if (!photoPaths)
{
photoPaths = [NSMutableArray array];
}
dispatch_async(dispatch_get_main_queue(), ^{
//block3. and finally if all ok I save image path, change load counter and post notification
if (path.length
&& ![photoPaths containsObject:path]
&& !haveDublicate
)
{
[photoPaths addObject:path];
[savedLinks setObject:photoPaths forKey:type];
entity.attachments = [NSKeyedArchiver archivedDataWithRootObject:savedLinks];
[self saveContext];
}
[RequestAPI sharedInstance].downloadsCount -= 1;
[[NSNotificationCenter defaultCenter]postNotificationName:kReloadFeedData object:nil];
});
});
});
});
}];
As dispatch_async says they will be executed asynchronous and not synchronous as you expected. Use dispatch_sync instead.
If you want to execute your code on a separate thread simple do the following
// create your thread
dispatch_queue_t queue = dispatch_queue_create("My Other Queue", 0);
// execute your synchronous block on the thread you've just created
dispatch_sync(queue,^{
// add your implementation here to be executed on your separate thread
dispatch_sync(dispatch_get_main_queue()^{
// update your UI here. Don't forget you can only update UI on the main thread
});
});