IOS: Search algorithm with proper GCD code - ios

I made my search algorithm for my UISearchBar, and I know that I must search in a background thread. Honestly I'm not familiar with multi-threading, so I'm looking for help. I'm using GCD (Grand Central Dispatch).
Here is my code, I want to know is it correct or not.
-(void)mySearchMethod
{
NSArray *allObjects = self.allMyObjects;
__block NSMutableArray *searchArray = [[NSMutableArray alloc] init];
dispatch_queue_t queue;
queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async( queue, ^{
for (MyObjectClass *myObjectAsDictionary in allObjects) {
for (NSString *titleSubString in [myObjectAsDictionary.title componentsSeparatedByString:#" "]) {
if ([[titleSubString lowercaseString] hasPrefix:[text lowercaseString]]) {
[searchArray addObject: myObjectAsDictionary];
}
}
}
dispatch_async( dispatch_get_main_queue(), ^{
self.tableObjects = searchArray;
[self.myTableView reloadData];
});
});
}
So does this code work in the background, or is it blocking the main thread?

Can the contents of allMyObjects change while the search is running? If so, you have two problems. First problem: you should not accessing an array on one thread while it's being mutated on another. You can fix this simply by copying the array: NSArray *allObjects = [self.allMyObjects copy];. Second problem: your search results might contain objects that have been removed from allMyObjects, or might be missing objects that have been added to allMyObjects. This is a hard problem to solve and how you solve it depends on your app.
What happens if the user cancels the search before your async block finishes running? Will it replace the contents of a table with search results even though the user doesn't want to see the results now?

Related

Loading data after one second in uitableview. Any alternate way to eliminate this delay

NSURL *url=[NSURL URLWithString:string];
dispatch_queue_t backgroundQueue = dispatch_queue_create("com.example.workQueue", DISPATCH_QUEUE_SERIAL);
dispatch_async(backgroundQueue, ^{
NSData *data=[NSData dataWithContentsOfURL:url];
NSDictionary *json=[NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];
NSLog(#"%#",json);
marray=[NSMutableArray array];
for (NSDictionary *dict in json) {
}
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
});
Is this the right way to handle data and reload the table in Objective C? If yes, then I still see some delay in seeing the data on tableview. Is there any way I can eliminate this delay? By the way this is second screen in my storyboard.
You are doing it all right. Download the data on background thread and handing it over to table view to reload data in main thread is all what you need to do. It's almost certainly your own delay (the network delay and the parsing time) in providing the data to the table, not the UITabvleView's delay in handling your reloadData call.
Some of the general rules to be followed when doing this:
Show loading overlay on screen when server call is going on.
Once data is returned pass it on to table view on main thread. Remove the loading overlay now.
Do not do heavy stuff in cellForRowAtIndexPath: method.
As a side note, although the same thingy but try once with below if you are following all above guidelines.
[self.tableView performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:YES];

How to make loop wait until block has finished?

I have an array of PFFiles I retrieve from parse. The PFFiles must be converted to images and I am trying to convert them in a loop, however;
The array of converted images must be in the same order of the array containing the PFFiles.
The problem is, is the loop runs and causes all of the blocks to trigger, then they are all loading at the same time thus the imageArray will result in a different order to the original array of PFFiles, because if one object finishes loading before the previous object, it will be added to the array before the previous one, making it go out of order.
Instead, I would like a loop where it loops through every object in the array, however it doesn't loop onto the next object until the getDataInBackgroundBlock has finished loading and the current object has been added to the new array.
-(void)organizePhotos {
for (PFFile *picture in pictureArray) {
//This block loads, after the next index in the loop is called, causing the array to be out of order.
[picture getDataInBackgroundWithBlock:^(NSData *data, NSError *error) {
[imageArray addObject:[UIImage imageWithData:data]];
[self savePhotos];
}];
}
}
Above is my code. Is there anyway I can make the loop wait until the getDatanBackgroundWithBlock finishes loading?
The problem is, is the loop runs and causes all of the blocks to trigger, then they are all loading at the same time
That is not a problem, it is good - the reason why the call is asynchronous is it can take an arbitrarily long time to complete. If you've got multiple downloads to do then doing then concurrently can be a big win.
thus the imageArray will result in a different order to the original array of PFFiles, because if one object finishes loading before the previous object, it will be added to the array before the previous one, making it go out of order.
This is the problem and can be addressed in a number of ways.
As you are using arrays, here is a simple array based solution: first create yourself an array of the right size and fill it with nulls to indicate the image hasn't yet arrived:
(all code typed directly into answer, treat as pseudo-code and expect some errors)
NSUInteger numberOfImages = pictureArray.length;
NSMutableArray *downloadedImages = [NSMutableArray arrayWithCapacity:numberOfImages];
// You cannot set a specific element if it is past the end of an array
// so pre-fill the array with nulls
NSUInteger count = numberOfImages;
while (count-- > 0)
[downloadedImages addObject:[NSNull null]];
Now you have your pre-filled array just modify your existing loop to write the downloaded image into the correct index:
for (NSUInteger ix = 0; ix < numberOfImages; ix++)
{
PFFile *picture = pictureArray[ix];
[picture getDataInBackgroundWithBlock:^(NSData *data, NSError *error) {
dispatch_async(dispatch_get_main_queue(),
^{ [imageArray replaceObjectAtIndex:ix
withObject:[UIImage imageWithData:data]
});
[self savePhotos];
}];
}
The use of dispatch_async here is to ensure there are not concurrent updates to the array.
If you wish to know when all images have been downloaded you can check for that within the dispatch_async block, e.g. it can increment a counter safely as it is running on the main thread and call a method/issue a notification/invoke a block when all the images have downloaded.
You are also possibly making things harder on yourself by using arrays, and trying to keep items in different arrays related by position. Dictionaries could save you some of the hassle, for example each of your PFFile objects presumably relates to a different URL, and a URL is a perfectly valid key for a dictionary. So you could do the following:
NSMutableDictionary *imageDict = [NSMutableDictionary new];
for (PFFile *picture in pictureArray) {
[picture getDataInBackgroundWithBlock:^(NSData *data, NSError *error) {
dispatch_async(dispatch_get_main_queue(),
^{ [imageDict setObject:[UIImage imageWithData:data] forKey:picture.URL];
};
[self savePhotos];
}];
}
And your can locate the image for a PFFile instance by looking up its URL in the dictionary - which will return nil if the image is not yet loaded.
There are other solutions, and you might want to look into making your code more asynchronous. Whatever you do trying to call asynchronous code synchronously is not a good idea and will impact the user experience.
HTH
Ideally, you should use a synchronous method; however the behavior you are asking for can be achieved using Grand Central Dispatch.
First, create a dispatch_group_t using dispatch_group_create(). Let's call it asyncGroup.
Then, before calling the async method, call dispatch_group_enter(asyncGroup). This increments the counter of the number of calls in the group.
Then, at the end of the async block, call dispatch_group_leave(asyncGroup) to decrement the counter of the number of calls in the group.
Finally, after calling the async method, call dispatch_group_wait(asyncGroup, timeout) to pause thread execution until the group counter reaches zero. Since you increment, make the call, and then wait, the loop will only continue when the async block has been run. Just ensure the timeout is longer than the operation will take.
You can make the code synchronous:
-(void)organizePhotos {
for (PFFile *picture in pictureArray) {
[imageArray addObject:[UIImage imageWithData:[picture getData]]];
[self savePhotos];
}
}
and run it in the background:
[self performSelectorInBackground:#selector(organizePhotos) withObject:nil];
You can use GCD approach which is to use dispatch_group. So, before you start an asynchronous task, call dispatch_group_enter, and then when the asynchronous task finishes, call dispatch_group_leave, and you can then create a dispatch_group_notify which will be called when the asynchronous tasks finish. You can marry this with a completion block pattern (which is a good idea for asynchronous methods, anyway):
Here is similar Question. Perhaps it may also be helpful:
How to wait for method that has completion block (all on main thread)?
You could do that using dispatch_async way, using semaphory, groups, but for your need better practice is using block to get response and do what you need.
Doing that:
-(void)organizePhotos:(void(^)(BOOL responseStatus))status {
for (PFFile *picture in pictureArray) {
//This block loads, after the next index in the loop is called, causing the array to be out of order.
[picture getDataInBackgroundWithBlock:^(NSData *data, NSError *error) {
[imageArray addObject:[UIImage imageWithData:data]];
status(YES);
}];
}
}
So you next at your call you could do that:
__weak typeof(self) weakSelf = self;
[self organizePhotos:^(BOOL responseStatus) {
if(responseStatus){
[weakSelf savePhotos];
}
}];
Or if you dont wan't create extra parameter response to your method you could do like this:
-(void)organizePhotos {
__weak typeof(self) weakSelf = self;
void (^ResponseBlock)(void) = ^{
[weakSelf savePhotos];
};
for (PFFile *picture in pictureArray) {
//This block loads, after the next index in the loop is called, causing the array to be out of order.
[picture getDataInBackgroundWithBlock:^(NSData *data, NSError *error) {
[imageArray addObject:[UIImage imageWithData:data]];
ResponseBlock();
}];
}
}
And another point that you are making wrong when calling "self" inside block, this could lead you to retain cycle and its a bad practice, look at my code how you should do.

UI freeze while saving images

I'm trying to save a NSDictionary containing 30 images. I'm calling the method to save the dictionary in the viewDidDisappear of my ViewController. The problem is that the UI freeze while saving. It's a small lag, less than a second, but a bit annoying. Do you have any ideas to make it more fluid? Maybe I should save the dictionary asynchronously, maybe in a block, but I don't know well how to use them.
Here's my saving et getting methods :
+ (NSDictionary*)getProgramImages{
NSString *path = [DataManager getProgramImagesFileDirectory];
NSDictionary *programImages = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
return programImages;
}
+ (void)saveProgramImages:(NSDictionary*)programImages{
NSString *path = [DataManager getProgramImagesFileDirectory];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:programImages];
[data writeToFile:path options:NSDataWritingAtomic error:nil];
}
Thanks a lot for your help!
Boris
You could try wrapping your function call using the below code, which uses Grand Central Dispatch to run that code on a background thread. Not able to test at the moment to see if that could solve your issue or not.
dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// call that function inside here
});
Maybe dispatch_async could help you to smoothen the code running on main thread.
dispatch_async(dispatch_get_main_queue(),^{
//your code goes here
});
There're many ways to solve your problem. You should read this
Concurrency Programming
Grand Central Dispatch is a good choice.

Completion blocks in for loop

Currently I am trying to do some async and concurrent tasks, and I am using Azures blob to upload all the images, however the concern is that, for every blob I need to get a SASURL and then upload the images. Also the another side towards it is that I want to have all the operations of the images completed to be uploaded, and hence send a final upload to the database. Although I can send the operation to the database earlier, without having the confirmation of the images completed, but I just wanted to make sure, that the operation does gets completed.
Below is the code for the SASURL block.
- (void)storageServiceBlob:(NSArray*)images
{
StorageService *storageService = [StorageService getInstance];
NSLog(#"%#",[storageService containers]);
NSLog(#"%#",[storageService blobs]);
for (int i = 0; i < [images count]; i++) {
NSString *file_name = [images objectAtIndex:i];
NSString *result = [self imageName:file_name];
NSLog(#"Final: %#", result);
[storageService getSasUrlForNewBlob:result forContainer:#"misccontainer" withCompletion:^(NSString *sasUrl) {
NSLog(#"%#",sasUrl);
[self postBlobWithUrl:sasUrl Image:[images objectAtIndex:i]];
}];
}
}
I want to use gcd in group somehow to determine that after all the completion blocks is called in a group, it executes Post method. Is there anyway to do this in gcd?
There are many ways you could do this. Here's one:
- (void)storageServiceBlob:(NSArray *)imageFilenames
{
StorageService *storageService = [StorageService getInstance];
__block NSMutableSet *remainingImageFilenames = [NSMutableSet setWithArray:imageFilenames];
for (NSString *imageFilename in imageFilenames) {
NSString *imageName = [self imageNameForImageFilename:imageFilename];
[storageService getSasUrlForNewBlob:imageName forContainer:#"misccontainer" withCompletion:^(NSString *sasUrl) {
[self postBlobWithUrl:sasUrl imageFilename:imageFileName];
[remainingImageFilenames removeObject:imageFilename];
if ([remainingImageFilenames count] == 0) {
// you're done, do your thing
}
}];
}
}
A few tips:
Be careful with your naming. There seems to be some ambiguity there.
Generally, idiomatic method name parameters start with a lower-case letter, e.g. myMethodWithThis:andThat:, not MyMethodWithThis:AndThat:.
Fast enumeration, e.g. for (id obj in array) is your friend. Learn and use it.
You can shortcut [array objectAtIndex:1] as array[1].
If you have access to the queue that the requests are going in then you can issue a barrier block.
When you have an async queue a barrier block will sit and wait to be executed until all of the blocks issued before it have run.
If you don't have access to the queue then your best bet is to keep a count.

Coredata magicalrecord fetching objects in background go in <fault> state

I'm using the MagicalRecord lib to load in some CoreData objects which are used to populate a tableview. This a slow operation thus I'm trying to do this on a background threat.
This all seems to go well, the data is loaded into self.products, only after a some time (+- minutes) all the loaded objects seem to go into <fault> state. I don't see why this is happening, anyone an idea ?
- (void)doInBackground
{
dispatch_queue_t myQueue = dispatch_queue_create("com.mycompany.myqueue", 0);
dispatch_async(myQueue, ^{
[self reloadData];
dispatch_sync(dispatch_get_main_queue(), ^{
[MBProgressHUD hideHUDForView:self.view animated:YES];
[self performSelectorOnMainThread: #selector(sortData:) withObject:self waitUntilDone:YES];
});
});
}
- (void)reloadData
{
NSArray *allProducts = [NSArray array];
allProducts = [NSArray arrayWithArray:[Product MR_findAll]];
self.products = [NSArray arrayWithArray:allProducts];
}
First, you are using, implicitly, the default context by only using MR_findAll without specifying a context. This will eventually lead to crashes or other unknown behavior. Second, you don't need all those NSArrays. The return value of MR_findAll (and all other MR_find* methods) return an array of results. No need for moving contents from array to array to array...
And lastly, your question. Because you're doing a fetch on the Main Context queue, your fetches will block the UI regardless. You need to make a Private Queue context and perform your fetches with that, like so:
NSManagedObjectContext *privateQueueContext = [NSManagedObjectContext MR_privateQueueContext];
NSArray *results = [Product MR_findAllInContext:privateQueueContext];
Also, you may want to look into batching your fetches. Look at the docs about getting a fetch request like so:
NSFetchRequest *request = [Product MR_requestAllInContext:privateQueueContext];
//update batch size here
MagicalRecord has a batch size set internally, but if you need to change it, this is the way you go about doing that.

Resources