how can I reduce time complexity from picking images from array - ios

That is how I have my data:
(
{
image = "https://www.myurl/abc- a/uploads/1161570652.jpg";
}
{
image = "https://www.myurl/abc- a/uploads/1161570652.jpg";
}
{
image = "https://www.myurl/abc- a/uploads/1161570652.jpg";
}
{
image = "https://www.myurl/abc- a/uploads/11615720652.jpg";
}
{
image = "https://www.myurl/abc- a/uploads/11615730652.jpg";
}
)
I could retrieve it successfully. But the problem is it is very slow.
Here is my code:
seeview=[[UIImageView alloc]init];
for (int k=0; k<[dictofimage count]; k++)
{
img = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:[image_array objectAtIndex:k]]]];
NSLog(#"image name is 0 %#",img);
[myimageview setImage:img];
}
It takes time to pick every element and is slow in progress. Is there any faster way to do it?

You can't get away from looping through all of the contents of image_array so you're stuck with O(n) but you can use a faster construct. The for:in loop is much faster than a typical C style loop, so something like:
for (imageString in image_array) {
img = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:imageString]]];
NSLog(#"image name is 0 %#",img);
[myimageview setImage:img];
}
For a breakdown of the benchmarks check out: http://iosdevelopertips.com/objective-c/high-performance-collection-looping-objective-c.html
EDIT: I should also note that dataWithContentsOfURL is a synchronous call that you probably shouldn't be making in this loop. You'd be better off creating an NSOperation that downloads the image, and when it finishes you can dispatch back to the main queue to set the actual image in the imageView.

You are better off handling the array of objects concurrently, in the background. This is precisely what dispatch_apply is for.
dispatch_apply(
image_array.count,
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),
^(size_t index) {
NSURL *url = [NSURL URLWithString:[image_array objectAtIndex:index]];
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]];
dispatch_async(dispatch_get_main_queue(), ^{
// Handle the image on the main thread for UI elements
});
});
You only need the dispatch_async if you need to handle the image on the main thread as part of a UI change.
Note, that dispatch_apply is synchronous, in that it kicks off one background task for each iteration. Those tasks run in the background, but dispatch_apply will not return until they are all done. Thus, you may also want to wrap that in an async block.
Or, you can do it yourself, and get notified when all the blocks are complete...
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
for (size_t i = 0; i < image_array.count; ++i) {
dispatch_group_async(group, queue, ^{
NSURL *url = [NSURL URLWithString:[image_array objectAtIndex:index]];
// The rest is the same...
});
}
dispatch_group_notify(group, queue, ^{
// This code will execute on `queue` when all the above blocks have finished
});
Note, however, that dispatch_apply has a nice algorithm to not overload the system. The above approach is not so kind.
However, you can get dispatch_apply nice-ness using an async call.
dispatch_async(queue, ^{
dispatch_apply(image_array.count, queue, ^(size_t index) {
NSURL *url = [NSURL URLWithString:[image_array objectAtIndex:index]];
// The rest is the same...
});
// This code will execute on `queue` when all the above blocks have finished
});

Related

Assign objects to variables outside asynchronous blocks

I am curious if it is possible to easily assign and retain __block objects inside asynchronous blocks in MRC and ARC? I obtained the following code while rewriting a piece of code with minimal changes. I got stuck when trying to return the image from an asynchronous block. A concern is that the code would one day be converted to ARC. I do not want to have hidden memory crash after the conversion. My options were
Using a GCD object holder if such a thing exists
Using a homebrew object holder, an array or others
Directly adding the image to the array (which I am using)
Rewriting the code to another structure
Basically the code load multiple images in a background thread. Searching // image deallocated :( will give you the location.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
ALAssetsLibrary *assetsLibrary = nil;
for (int i = 0; i < sources.count; ++i) {
__block UIImage *image = nil;
id source = sources[i];
if ([source isKindOfClass:[NSString class]]) {
image = something
} else if ([source isKindOfClass:[NSURL class]]) {
NSURL *url = source;
if ([url.scheme isEqualToString:#"assets-library"]) {
dispatch_group_t group = dispatch_group_create();
if (!assetsLibrary)
assetsLibrary = [[[ALAssetsLibrary alloc] init] autorelease];
dispatch_group_enter(group);
[assetsLibrary assetForURL:url resultBlock:^(ALAsset *asset) {
image = something
dispatch_group_leave(group);
} failureBlock:^(NSError *error) {
dispatch_group_leave(group);
}];
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
// image deallocated :(
} else if (url.isFileURL) {
image = something
}
}
// add image to an array
}
dispatch_async(dispatch_get_main_queue(), ^{
// notify
});
});
I think moving the image variable declaration would be the first right step to think about this. You can't expect to have one image variable for multiple concurrent operations.
so to get started, let's do this-
ALAssetsLibrary *assetsLibrary = nil;
for (int i = 0; i < sources.count; ++i) {
__block UIImage *image = nil;
After that, you are currently keeping UIImage instances in an array? This is not recommended. Please find out a way to write these images to documents directory and later read from there when needed. Keeping UIImage instances in memory will lead to severe memory overuse issues.
You need to come up with a naming convention to write your images into documents directory. Whenever an image load completes, just save it to your local folder and later read from there.
You could also call the notify completion part as soon as one image is loaded and keep notifying about others as you get them. Not sure how your design is working.
Also, I noticed you are creating multiple groups. One new group with each iteration inside for loop here-
dispatch_group_t group = dispatch_group_create();
Moving it outside for loop would be a good call.
Hope this helps.

dispatch_get_main_queue() not running perfect well

I am Newbie in iOS Development. I have no knowledge about dispatch_get_main_queue() so I want to get image size from my Server image url like as
First I parse my JSON Data and Get image Size like as
[self.feedArray addObjectsFromArray:[pNotification.userInfo valueForKey:#"items"]];
[self fillHeightArray];
Here I set parse data in my self.feedArray and after that I get Height like as
-(void)fillHeightArray
{
NSMutableArray *requestArray=[[NSMutableArray alloc]init];
NSMutableArray *dataArray=[[NSMutableArray alloc]init];
for (int i=0; i<[self.feedArray count];i++)
{
NSString *urlString = [[self.feedArray objectAtIndex:i]valueForKey:#"photo"];
NSURL *imageFileURL = [NSURL URLWithString:urlString];
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:imageFileURL];
[requestArray addObject:urlRequest];
}
dispatch_queue_t callerQueue = dispatch_get_main_queue();
dispatch_queue_t downloadQueue = dispatch_queue_create("Lots of requests", NULL);
dispatch_async(downloadQueue, ^{
for (NSURLRequest *request in requestArray) {
[dataArray addObject:[NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil]];
}
dispatch_async(callerQueue, ^{
for (int i=0; i<[dataArray count]; i++)
{
UIImage *imagemain=[UIImage imageWithData:[dataArray objectAtIndex:i]];
UIImage *compimage =[self resizeImage:imagemain resizeSize:CGSizeMake(screenWidth/2-16,180)];
CGSize size = CGSizeMake(screenWidth/2-16,compimage.size.height);
[self.cellHeights addObject:[NSValue valueWithCGSize:size]];
}
[GlobalClass StopSpinner:self.view];
self.cltItem.hidden=FALSE;
[self.cltItem reloadData];
[self.cltItem.collectionViewLayout invalidateLayout];
[[NSUserDefaults standardUserDefaults]setValue:#"1" forKey:Loading];
});
});
}
And resize my image like as
-(UIImage *)resizeImage:(UIImage *)orginalImage resizeSize:(CGSize)size
{
float oldWidth = orginalImage.size.width;
float scaleFactor = size.width / oldWidth;
float newHeight = orginalImage.size.height * scaleFactor;
float newWidth = oldWidth * scaleFactor;
UIGraphicsBeginImageContext(CGSizeMake(newWidth, newHeight));
[orginalImage drawInRect:CGRectMake(0,0,newWidth,newHeight)];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
So, from this code i got fine result at first time but when i want to load more data so this code are Running second time then i got not valid image size
I don't understand what the issue is there, but I think my
dispatch_queue_t callerQueue = dispatch_get_main_queue();
dispatch_queue_t downloadQueue = dispatch_queue_create("Lots of requests", NULL);
Have issue when I load more data.
Please help me for that.
You are always adding objects in this line:
[self.cellHeights addObject:[NSValue valueWithCGSize:size]];
When you run the code a second time, the array gets bigger, and the old values are still present in its beginning. This is probably giving you bad results when running the code a second time.
EDIT:
It might work slower, because you've made some retain cycles / have memory leaks. In this scenario, it will work ok the first time, and slower for each extra run. I do not really see anything wrong with your code, besides the self.cellHeights table growing. Check the rest of the procedure for elements that are getting bigger every time, and ensure that the objects that are not going to be used anymore are getting released.
Also, try using the 'build & analyze' [ALT + CMD + B]. This might point you to some memory leaks, or other issues.
Profiling tools are also very efficient in localizing leaks, and you can access them with [CMD + I] on the keyboard.
Another thing you can try is calling the main_queue directly, like:
dispatch_async(dispatch_get_main_queue(), ^(void) {
//do sth
});
You will avoid creating another object, and you only use the main_queue once in the whole snippet anyway.
Try doing this and let me know if you got anything.

Multiple webservice Making UI unresponsive

The problem is that I am calling multiple webservices on my homepage and the webservice is returning me the images and text from the server. During this process the UI become fully unresponsive for say 1-2 minutes which is looking very bad as I cant do anything. I heard about dispatch and tried to implement it but I dont get any results.May be I am doing something wrong.
What I want now that I want to that I want to run this process in background So that a user can interact with the UI during the fetching operation from the server. I am implementing my code just tell me where to use dispatch.
-(void)WebserviceHomeSlider{
if([AppDelegate appDelegate].isCheckConnection){
//Internet connection not available. Please try again.
UIAlertView *alertView=[[UIAlertView alloc] initWithTitle:#"Internate error" message:#"Internet connection not available. Please try again." delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil, nil];
[alertView show];
return;
}else {
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.responseSerializer.acceptableContentTypes = [manager.responseSerializer.acceptableContentTypes setByAddingObject:#"text/html"];
[manager GET:ServiceUrl#"fpMainBanner" parameters:nil success:^(AFHTTPRequestOperation *operation,id responseObject)
{
//NSLog(#"JSON: %#", responseObject);
arrSlider = [responseObject objectWithJSONSafeObjects];
[_slideshow setTransitionType:KASlideShowTransitionSlide];
_slideshow.gestureRecognizers = nil;
[_slideshow addGesture:KASlideShowGestureSwipe];
// [_slideshow addImagesFromResources:[self getImages]]; // Add
// [_slideshow addTextFromResources:[self getText]];
// [slideShow subtextWithImages:[self getsubText]];
[_slideshow addImagesFromResources:[self getImages] stringArray:[self getText] stringsubArray:[self getsubText]];
}
failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
}
}
Just tell me where to use dispatch or edit my code using dispatch if possible. I have gone through some examples but still my concept is not clear. Which dispatch method id best (DEFAULT or BACKGROUND). I will be very thankful to You.
This is the code you are looking for . Just tell me where to edit it using dispatch
-(NSArray *)getText{
NSMutableArray *textArr = [[NSMutableArray alloc] init];
for(int i=0; i<[arrSlider count];i++)
{
texxt=[[arrSlider objectAtIndex:i ]valueForKey:#"title" ];
[textArr addObject:[texxt uppercaseString]];
}
return textArr;
}
-(NSArray *)getsubText{
NSMutableArray *subtext = [[NSMutableArray alloc] init];
for(int i=0; i<[arrSlider count];i++)
{
subbtext=[[arrSlider objectAtIndex:i ]valueForKey:#"tagline_value" ];
if(i==8)
{
subbtext=#"MAKE YOURSELF STAND OUT GET YOUR FREE CARDS!";
}
NSLog(#"subtext is,,.,.,,.,%#.%#",#"k",subbtext);
[subtext addObject:[subbtext uppercaseString]];
}
return subtext;
}
-(NSArray *)getImages
{
NSMutableArray *mArr = [[NSMutableArray alloc] init];
for(int i=0; i<[arrSlider count];i++)
{
pathh=[[arrSlider objectAtIndex:i ]valueForKey:#"filepath" ];
NSString *newString = [pathh stringByReplacingOccurrencesOfString:#" " withString:#"%20"];
NSURL *imageURL = [NSURL URLWithString:newString];
NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
UIImage *originalImage = [UIImage imageWithData:imageData];
CGSize destinationSize = CGSizeMake(320, 158);
UIGraphicsBeginImageContext(destinationSize);
[originalImage drawInRect:CGRectMake(0,0,destinationSize.width,destinationSize.height)];
UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// [whotshotimgview setImage:image];
[mArr addObject: img];
}
return mArr;
}
[_slideshow addImagesFromResources:[self getImages] stringArray:[self getText] stringsubArray:[self getsubText]];
The problem here is that you didn't showed implementation of addImagesFromResources method. You probably have to implement GCD in this method because i guess you are fetching images and setting on UI by this your main thread is getting blocked.
You are trying to access the UIView from thread other than the main which causes the UI unresponsiveness.
Please use this:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[_slideshow setTransitionType:KASlideShowTransitionSlide];
_slideshow.gestureRecognizers = nil;
[_slideshow addGesture:KASlideShowGestureSwipe];
// [_slideshow addImagesFromResources:[self getImages]]; // Add
// [_slideshow addTextFromResources:[self getText]];
// [slideShow subtextWithImages:[self getsubText]];
[_slideshow addImagesFromResources:[self getImages] stringArray:[self getText] stringsubArray:[self getsubText]];
});
What I think is that your UI hangs due to downloading of image.
So you should use SDWebImage library that can help you to cache image and also prevent the UI getting hang. Using SDWebImage you can show the loader for the time the image is not loaded and it will cache the image. So from next time no image will be downloaded and also the UI will not hang. The link for complete reference to SDWebImage is :https://github.com/rs/SDWebImage
This block blocking your main UI to perform tasks so update this block as follow,
for(int i=0; i<[arrSlider count];i++)
{
pathh=[[arrSlider objectAtIndex:i ]valueForKey:#"filepath" ];
NSString *newString = [pathh stringByReplacingOccurrencesOfString:#" " withString:#"%20"];
dispatch_queue_t myQueue = dispatch_queue_create("My Queue",NULL);
dispatch_async(myQueue, ^{
NSURL *imageURL = [NSURL URLWithString:newString];
NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
UIImage *originalImage = [UIImage imageWithData:imageData];
CGSize destinationSize = CGSizeMake(320, 158);
UIGraphicsBeginImageContext(destinationSize);
[originalImage drawInRect:CGRectMake(0,0,destinationSize.width,destinationSize.height)];
UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// [whotshotimgview setImage:image];
[mArr addObject: img];
dispatch_async(dispatch_get_main_queue(), ^{
// If you want to refresh your UI simultaniously, you can write statement for that i.e
// yourImageView.image = img;
// Otherwise remove this queue
});
});
}
The main thread should never be used for long processing. Its there for the UI.
AFNetworking provides asynchronous functionality for downloading files. You should also be careful of thrashing the network layer with too many simulatanous downloads. I think 4 or 5 is the max the last time I tested.
So the flow of execution should be as follows.
The controller sets up the slideshow, sends the downloading of the images to a background thread(automatically handled by AFNetworking), passing a completion block to it. In this completion block you need to dispatch the work back to the main thread using GCD, dispatch_async to dispatch_get_get_main_queue
Keep your main thread's run loop available for user interaction.
It is because the images are loading in another thread and you are populating your slider right after getting the data.
It is better to implement this protocol method of KASlidershow to use the slideshow in a more memory efficient way.
(UIImage *)slideShow:(KASlideShow *)slideShow imageForPosition:(KASlideShowPosition)position
{
pathh=[[arrSlider objectAtIndex:i ]valueForKey:#"filepath" ];
NSString *newString = [pathh stringByReplacingOccurrencesOfString:#" " withString:#"%20"];
dispatch_queue_t myQueue = dispatch_queue_create("My Queue",NULL);
dispatch_async(myQueue, ^{
NSURL *imageURL = [NSURL URLWithString:newString];
NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
UIImage *originalImage = [UIImage imageWithData:imageData];
CGSize destinationSize = CGSizeMake(320, 158);
UIGraphicsBeginImageContext(destinationSize);
[originalImage drawInRect:CGRectMake(0,0,destinationSize.width,destinationSize.height)];
UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// [whotshotimgview setImage:image];
[mArr addObject: img];
return img;
});
}
i didn't try the code but i hope it will work with little changes in variable names. Good luck

fetching image from URL taking too much time in watch app

I am fetching an image from a URL to set it as a background image of WKInterfaceGroup in a watch app but it is taking too much time (around 20 seconds) to load the background image, I seem to have used the most preferred method but I am not able to figure out why it is taking so long to load an image... The following is my code:
-(void) watchData:(WatchJSONParser *)data
{
int type = (int)data.sourceType;
watchUserData = data.watchDataDict;
NSLog(#"watchUserData:%#", watchUserData);
switch (type)
{
case DataConnectionSourceTypeUserData:
if([watchUserData objectForKey:#"userData"])
{
skpImgServerUrl = [[watchUserData objectForKey:#"userData"] objectForKey:#"imageServerURL"];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
^{
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"%#%#",skpImgServerUrl,[[watchUserData objectForKey:#"userData"] objectForKey:#"tenantLogo"]]];
NSData *data = [NSData dataWithContentsOfURL:url];
NSLog(#"ASY thread:%#", url);
dispatch_async(dispatch_get_main_queue(), ^{
//[self.homeBgGroup setBackgroundImage:placeholder];
[self.homeBgGroup setBackgroundImageData:data];
[statusLabel setHidden:YES];
});
});
}
break;
default:
break;
}
}

UI got a little stuck since fetching a photo

Now I'm using this code, with a little modification:
if (self._photoPath && !self._photo) {
dispatch_queue_t bg_thread = dispatch_queue_create("com.yourcompany.bg_thread", NULL);
dispatch_queue_t main_queue = dispatch_get_main_queue();
dispatch_async(bg_thread,^{
NSData *data = [NSData dataWithContentsOfFile:self._photoPath];
if(data != nil) {
dispatch_async(main_queue,^{
self._photo = [UIImage imageWithData:data];
[self.photoButton setImage:[UIImage imageNamed:#"photoButton.png"] forState:UIControlStateNormal];
});
}
});
}
As you can see, in fact after I get that photo I want set it immediately to my "photoButton",
but now, UI got smoothed, but my photoButton's appearance is always black...
What should I do next?
_______________________Updated___________________
I have 2 viewControllers, A and B.
A is the root viewController, and B is A's child viewController.
In B, there is a button for calling the camera to take a photo.
After user took a photo, the photo's appearance becomes that photo.
When I push a new B (with no photo) from A,
things goes smoothly.
But when there is an old B with a photo in it,
the animation gets a little stuck, caused by the following code I guess:
- (void)viewWillAppear:(BOOL) animated {
if (self._photoPath && !self._photo) {
NSData *data = [NSData dataWithContentsOfFile:self._photoPath];
if(data != nil)
self._photo = [UIImage imageWithData:data];
}
[super viewWillApear];
}
But I do need to get that photo before the view is displayed since I need to set that photo to my photoButton's background.
So, is there a way to avoid sticking the view's animation? Because it really result in bad user experience.
Try fetching the photo in a backgroudn thread (I'm using GCD here):
- (void)viewWillAppear:(BOOL) animated {
if (self._photoPath && !self._photo) {
dispatch_queue_t bg_thread = dispatch_queue_create("com.yourcompany.bg_thread", NULL);
dispatch_queue_t main_queue = dispatch_get_main_queue();
dispatch_async(bg_thread,^{
NSData *data = [NSData dataWithContentsOfFile:self._photoPath];
if(data != nil) {
dispatch_async(main_queue,^{ self._photo = [UIImage imageWithData:data]; });
}
});
}
[super viewWillApear];
}

Resources