I get EXC_BAD_ACCESS when MaxConcurrentOperationCount > 1 - objective-c

Hello i am using NSOperationQueue to download images in the background. I have created a custom NSOperation to download the images. I put the images in table cells. The problem is if I do [operationQueue setMaxConcurrentOperationCount: 10] and i scroll down several cells the program crashes with EXC_BAD_ACCESS. Every time it crashes at the same place in the table. There are 3 cells one after the other which are for the same company and have the same logo so basically it should download the images 3 times. Every other time it works fine.
- (void) main
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
NSURL *url = [[NSURL alloc] initWithString:self.imageURL];
debugLog(#"downloading image: %#", self.imageURL);
//NSError *error = nil;
NSData *data = [[NSData alloc] initWithContentsOfURL:url];
[url release];
UIImage *image = [[UIImage alloc] initWithData:data];
[data release];
if (image)
{
if (image.size.width != ICONWIDTH && image.size.height != ICONHEIGHT)
{
UIImage *resizedImage;
CGSize itemSize = CGSizeMake(ICONWIDTH, ICONHEIGHT);
//!!! UIGraphicsBeginImageContext NOT THREAD SAFE
UIGraphicsBeginImageContext(itemSize);
CGRect imageRect = CGRectMake(0.0, 0.0, itemSize.width, itemSize.height);
[image drawInRect:imageRect];
resizedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
self.theImage = resizedImage;
}
else
{
self.theImage = image;
}
[image release];
}
[delegate didFinishDownloadingImage: self];
[pool release];
}
This is how i handle downloading the images. If i comment out
[delegate didFinishDownloadingImage: self];
in the function above it doesn't crash but of course it is useless.
-(void) didFinishDownloadingImage:(ImageDownloadOperation *) imageDownloader
{
[self performSelectorOnMainThread: #selector(handleDidFinishDownloadingImage:) withObject: imageDownloader waitUntilDone: FALSE];
}
-(void) handleDidFinishDownloadingImage:(ImageDownloadOperation *)imageDownloadOperation
{
NSArray *visiblePaths = [self.myTableView indexPathsForVisibleRows];
CompanyImgDownloaderState *stateObject = (CompanyImgDownloaderState *)[imageDownloadOperation stateObject];
if ([visiblePaths containsObject: stateObject.indexPath])
{
//debugLog(#"didFinishDownloadingImage %# %#", imageDownloader.theImage);
UITableViewCell *cell = [self.myTableView cellForRowAtIndexPath: stateObject.indexPath];
UIImageView *imageView = (UIImageView *)[cell viewWithTag: 1];
if (imageDownloadOperation.theImage)
{
imageView.image = imageDownloadOperation.theImage;
stateObject.company.icon = imageDownloadOperation.theImage;
}
else
{
imageView.image = [(TestWebServiceAppDelegate *)[[UIApplication sharedApplication] delegate] getCylexIcon];
stateObject.company.icon = [(TestWebServiceAppDelegate *)[[UIApplication sharedApplication] delegate] getCylexIcon];
}
}
}

According to this mailing list post, UIGraphicsBeginImageContext isn't thread safe. The post hints that CGBitmapContextCreate and related functions are the safe way to do it.
http://osdir.com/ml/cocoa-dev/2009-10/msg00035.html

I think you're crashing because you're trying to access cells in the tableview that are not there.
Regardless of how long the logical table is, the visual tableview only holds enough cells to display the section of the logical table currently on screen. At the default row size, a table shows and therefore contains, only 9 to 10 cell objects. For example, if you have a logical table that is 100 rows long and your view is showing rows 11-20 the table only has 9 cell objects. If you show rows 89-98, it only has the exact same 9 cell objects. No matter which rows you display, you see the same 9 cell objects over and over again. The only thing that changes is the data they display.
If you try to access the cell for a logical row off screen, you won't get anything back. In your case, you try to access the 11th logical row but there is no 11th cell and there never will be.
I think you have some conceptual confusion because you're trying to store data in the tableview itself by setting the cell contents. This will not work because tableviews do not store any data beyond what is immediately displayed. When a tableview needs to display more rows as you scroll, it reuses the existing cell and its DataSource delegate changes the data that the existing cells display.
Instead of storing the images in the cells, you need to create a data model and store the images there. Then when the tableview scrolls, it will send tableview:cellForRowAtIndexPath: to its datasource delegate. The datasource delegate will then ask the data model for the data for the logical row, populate the reused cell and return it to the tableview.

Ok it seems it is solved thanks to codewarrior. This is the part that changed.
#synchronized(delegate)
{
UIImage *resizedImage;
UIGraphicsBeginImageContext(itemSize);
CGRect imageRect = CGRectMake(0.0, 0.0, itemSize.width, itemSize.height);
[image drawInRect:imageRect];
resizedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
self.theImage = resizedImage;
}

Related

Removing UIImageView and getting the memory back (my memory is leaking)

I am making a simple drawing application, but a have problems with memory. After a few minutes using it, it just collapse, it makes the iPad very slow.
The lines the user create drawing are made of a UIImageView of 2x2 pixel, so it's this UIImageView is created several times:
UIImageView*dibujo = [[UIImageView alloc] initWithFrame:CGRectMake(x-1,y-1,2,2)];
dibujo.image = [UIImage imageNamed:#"pixelde2po2.png"];
tagdedibujo=tagdedibujo+1;
dibujo.tag=tagdedibujo;
[self.view addSubview:dibujo];
And it's being erased in this way:
for (int aa=ultimotag+1; aa<=tagdedibujo; aa++) {
//NSLog(#"destruyendo: %i", aa);
UIImageView*dibujo1 = (UIImageView *)[self.view viewWithTag:aa];
[UIView animateWithDuration:0.5 animations:^{
dibujo1.alpha=0;
}];
}
[self performSelector:#selector(matardibujo) withObject:(self) afterDelay:(0.51)];
ultimotag=tagdedibujo;
-(void) matardibujo{
for (int aa=ultimotag+1; aa<=tagdedibujo; aa++) {
[[self.view viewWithTag:aa] removeFromSuperview];
}
}
Even after erasing de draw (matardibujo) i don't get the memory back. Even it grows. So i think the erasing part is the one that's messing here.
I would preciate your help, i'm kind of new in this. Thanks :).
Could you replace dibujo.image = [UIImage imageNamed:#"pixelde2po2.png"]; with the following line of code to see if it resolves your issue?
NSString *fileName = [[NSBundle mainBundle] pathForResource:#"pixelde2po2" ofType:#"png"];
UIImage *image = [UIImage imageWithContentsOfFile:fileName];

Objective-C: UILabel "stuck" in my view

I'm working on a tabbed app that (among other things) creates a simple Facebook feed. The Facebook section is a UINavigationController holding a Master Table View and a Detail View. When I click one of the rows in the Master Table View, it brings up the detail view (and the appropriately filled labels) as intended. However, if I go back to the Master View and select another row, something strange happens. If the "detailLab" label (which is sizeToFit'd) was, say, four lines long on the first occurrence of Detail View, and the "detailLab" label was 2 lines long on the second occurrence of Detail View, then on the second occurrence of Detail View, "detailLab" will display the first two lines properly, but underneath them will be the last two lines from the first occurrence of Detail View.
A picture of this happening.
And some (hopefully) relevant code:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
CGRect labelFrame = CGRectMake(100, 40, 220, 150);
detailLab = [[UILabel alloc] initWithFrame:labelFrame];
[detailLab setTextColor:[UIColor darkGrayColor]];
detailLab.font = [UIFont systemFontOfSize:14];
[detailLab setText:detail];
[detailLab setNumberOfLines:0];
[detailLab sizeToFit];
[self.view addSubview:detailLab];
NSURL *url = [NSURL URLWithString:pictureString];
NSData *imageData = [[NSData alloc] initWithContentsOfURL:url];
picture = [UIImage imageWithData:imageData];
bigImage.image = picture;
NSLog(#"dateString: %#", dateString);
dateLabel.text = dateString;
}
- (void)viewDidUnload
{
[super viewDidUnload];
bigImage = nil;
detailLab = nil;
detailLab.hidden = YES;
// Release any retained subviews of the main view.
self.detailDescriptionLabel = nil;
}
If you need any other code or details, just ask; I'd be happy to provide it.
The problem is that each time viewWillAppear is called you are creating a new label without, I assume, removing it in viewWillDisappear. So the labels are stacking up, one on top of the other.
Remember you may get called like this (ignoring irrelevant calls):
viewDidLoad
viewWillAppear
viewWillDisappear
viewWillAppear <- Whoops! Two labels
viweWillDisappear
viewDidUnload
A neater solution would be to add your label view in viewDidLoad and only alter the position of that already existing view in viewWillAppear. You still need to remember to dispose of the view in viewDidUnLoad.
Here's what I mean (note this was only eyeball compiled so you may need to correct some typos):
- (void)viewDidLoad
{
[super viewDidLoad];
detailLab = [[UILabel alloc] initWithFrame:CGRectZero];
[detailLab setTextColor:[UIColor darkGrayColor]];
detailLab.font = [UIFont systemFontOfSize:14];
[self.view addSubview:detailLab];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
CGRect labelFrame = CGRectMake(100, 40, 220, 150);
[detailLab setFrame:labelFrame];
[detailLab setText:detail];
[detailLab setNumberOfLines:0];
[detailLab sizeToFit];
NSURL *url = [NSURL URLWithString:pictureString];
NSData *imageData = [[NSData alloc] initWithContentsOfURL:url];
picture = [UIImage imageWithData:imageData];
bigImage.image = picture;
NSLog(#"dateString: %#", dateString);
dateLabel.text = dateString;
}
- (void)viewDidUnload
{
[super viewDidUnload];
bigImage = nil;
detailLab = nil;
detailLab.hidden = YES;
// Release any retained subviews of the main view.
self.detailDescriptionLabel = nil;
}
That is normal with Master View to Detail Views - Frustrating, but Normal. The solution I found when I had the problem was to reset the label at the top of each -ViewDidLoad.
One option would be to remove it in viewDidUnload. Another would be to reset the label each time, using a segment of code like the one below, positioned above the section of your code where it actually loads in the content
detailLab = [NSString stringWithFormat:#""];
The issue is that with the setup of most Master/Detail views, it's not actually creating new detail views each time you add a view, it's just creating another link to the same view in the master view section. This is fine, but you just need to remember to use viewDidUnload lots
Hope it Helps

App get crashed while loading large amount of data in UITableview

I am showing more than 10 columns in uitableview cell and having more than 100 rows but when i scroll tableview or reload table it received memory warning and app get crashed.
And we are also using collapse and expand on this table view.
Following code applied in cellforrowatindexpath
for(i=0;i<[appDel.tempItemDic count];i++)
{
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(CNT+30, -30, 350,100)];
label.font = [UIFont systemFontOfSize:14.0];
label.text =[tempDic objectForKey:[appDel.arrColumnList objectAtIndex:[[appDel.tempArr objectAtIndex:i]intValue]]];
label.textAlignment = UITextAlignmentLeft;
label.textColor = [UIColor whiteColor];
label.backgroundColor=[UIColor clearColor];
label.numberOfLines=1;
[cell.contentView addSubview:label];
[label release];
backgroundView.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"ListItems_secondLevel(3).png"]];
cell.backgroundView = backgroundView;
cell.imageView.image=[UIImage imageNamed:#"arrow.png"];
CNT+=350;
}
You need to create the UILabels only while creating the cell. While reusing the cell, you can get the UILabel using tag and set the value to that. Do not create again and again.
Edit:
In addition to that, as Sarah mentioned, you don't need to do that in loop. As for every cell the method will get called, and you can set the value depend on the indexPath.
you don't need for loop in the method. As objectatIndex works for the same. remove the for loop as you are already using objectAtIndex and try again.
Probably you're not reusing the cell. So if you create 100 cells and each cell has 10 labels at the you will have UILabel views all kept in memory.
You should reuse the cell in this way (code not tested, just to give you an idea, I'm not commenting the way you create the text, I'm concerned only about memory warning):
-(UITableViewCell *)tableView:(UITableView *)tv cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// add your code here to get the number of columns; I assume you defined a "getNumberOfColumnsForRowAtIndexPath:" somewhere in your code to retrieve this figure
NSInteger numberOfColumns = [appDel getNumberOfColumnsForRowAtIndexPath:indexPath];
NSString *cellId=[NSString stringWithFormat:#"MyComplexCellID_%d",numberOfColumns];
UITableViewCell *cell = [tv dequeReusableCellWithIdentifier:cellId];
if(!cell) {
// here you create the cell and add the labels; DON'T GIVE TEXT TO THE LABELS HERE
// in order to get the right label give them a tag; e.g. 500 is column 0, 501 column 1,...
for(int i=0;i<numberOfColumns;i++) {
UILabel *myLabel = ...
myLabel.tag=500+i;
}
}
// here you configure the cell
for(int i=0;i<numberOfColumns;i++) {
NSString *columnText = retrieve here your column text;
UILabel *labelColumn = (UILabel *)[cell.contentView viewWithTag:500+i];
labelColumn.text = columnText;
}
return cell;
}
If you are loading a large amount of data in table view I will suggest you
1:-
(void)viewDidLoad {
[super viewDidLoad];
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableview reloadData];
});
}
2:-
(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_async(q, ^{
NSLog(#"DISPATCH_QUEUE_PRIORITY_HIGH - Main Thread");
[self methodForServerResponse];
});
}
In that methodForServerResponse-
(void)methodForServerResponse{
dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_async(q, ^{
NSLog(#"DISPATCH_QUEUE_PRIORITY_HIGH - Main Thread within Main Thread");
//implement what you stuff need from server
});

problem with UiImageView.image = nil

.h file
#interface AppViewController : UIViewController {
...
UIImageView *imageViewArray[3][5];// not using any #property for this
...
}
...
#define FOR(j,q) for (int j = 0; j < q; j++)
.m file
ViewDidLoad{
FOR(i,5){
FOR(j,3)
{
image_names[0] = [UIImage imageNamed:[NSString stringWithFormat:#"%d.png",j]];
imageViewArray[j][i] = [[UIImageView alloc] initWithImage: image_names[0]];
CGRect newFrame ;
newFrame = CGRectMake(60+(j*130), 110+(150*i),130,136);
imageViewArray[j][i].frame = newFrame;
[self.view addSubview:imageViewArray[j][i]];
}
}
}
//clear the previous images and call a method to add new
-(IBAction)Change{
FOR(i,5)
FOR(p,3)
imageViewArray[p][i].image = nil;
[self ImageSwitch];
}
-(void)ImageSwitch{
FOR(i,5){
FOR(j,3)
{
image_namesTmp[0] = [UIImage imageNamed:[NSString stringWithFormat:#"%d.png",j+3]];
imageViewArray[j][i] = [[UIImageView alloc] initWithImage: image_namesTmp[0]];
CGRect newFrame ;
newFrame = CGRectMake(60+(j*130), 110+(150*i),130,136);
imageViewArray[j][i].frame = newFrame;
[self.view addSubview:imageViewArray[j][i]];
}
}
}
when i press the button for the first time it works fine(old images get replaced with new ones),but if i do it second time
imageViewArray[p][i].image = nil;
this line wont work and all the previous images still remain there and new images are overlapping with the present ones
In the ImageSwitch method you are always creating new instances of UIImageView.
imageViewArray[j][i] = [[UIImageView alloc] initWithImage: image_namesTmp[0]];
At this point, there already is a reference to UIImageView stored in the variable. You are losing your reference to it and since you alloc/inited it, you will most probably also leak its memory. Moreover, you are adding new UIImageViews to the view hierarchy, but you are not removing the old ones.
Try something like this:
[imageViewArray[j][i] removeFromSuperview];
[imageViewArray[j][i] release];
imageViewArray[j][i] = [[UIImageView alloc] initWithImage: image_namesTmp[0]];
Second time you set your images you don't need to recreate the UIImageView objects. Your ImageSwitch method is better declared as,
-(void)ImageSwitch{
FOR(i,5){
FOR(j,3)
{
imageViewArray[j][i].image = [UIImage imageNamed:[NSString stringWithFormat:#"%d.png",j+3]];
}
}
}
Just changing the images will suffice and that's what you need to do.
You need an NSMutableArray of ImageViews. In your header declare the NSMutableArray:
NSMutableArray *_imageViewArray;
Then in your viewDidLoad method initialize the array and then populate it with your image views.
_imageViewArray = [[NSMutableArray alloc]init];
for (int i =0; i < imageNames; i++) {
UIImageView *tempImageView = [[UIImageView alloc]initWithImage:[UIImage imageNamed:[imageNames objectAtIndex:i];
[_imageViewArray addObject:tempImageView];
[tempImageView release];
}
So thats the image view array populated, you may do it a different way but thats up to you.
To switch the views over what I would do is like so...
[self.view removeAllSubviews]; // gets rid of all previous subviews.
[self.view addSubview:[_imageViewArray objectAtIndex:3]];// could be any number. generate a random number if you want.
Thats all you would need to do. If you wanted to know which image view was being displauyed the maybe declare an instance of UIImageView in your header called _currentImageView and then you can remove just that image view instead of all subviews.
Hope this helped.

Creating a method for wrapping a loaded image in a UIImageView

I have the following un my applicationDidFinishLaunching method
UIImage *image2 = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"image2.jpg" ofType:nil]];
view2 = [[UIImageView alloc] initWithImage:image2];
view2.hidden = YES;
[containerView addSubview:view2];
I'm simply adding an image to a view. But because I need to add 30-40 images I need to wrap the above in a function (which returns a UIImageView) and then call it from a loop.
This is my first attempt at creating the function
-(UIImageView)wrapImage:(NSString *)imagePath
{
UIImage *image = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle]
pathForResource:imagePath
ofType:nil]];
UIImageView *view = [[UIImageView alloc] initWithImage:image];
view.hidden = YES;
return view;
}
And then to invoke it I have the following so far, for simplicity I am only wrapping 3 images
//Create an array and add elements to it
NSMutableArray *anArray = [[NSMutableArray alloc] init];
[anArray addObject:#"image1.jpg"];
[anArray addObject:#"image2.jpg"];
[anArray addObject:#"image3.jpg"];
//Use a for each loop to iterate through the array
for (NSString *s in anArray) {
UIImageView *wrappedImgInView=[self wrapImage:s];
[containerView addSubview:wrappedImgInView];
NSLog(s);
}
//Release the array
[anArray release];
I have 2 general questions
Is my approach correct?, ie following best practices, for what I am trying to do (load a multiple number of images(jpgs, pngs, etc.) and adding them to a container view)
For this to work properly with a large number of images, do I need to keep my array creation separate from my method invocation?
Any other suggestions welcome!
Just a note, in function declaration you should return pointer to UIImageView, not an UIImageView itself (i.e. add an asterisk).
Also, when returning the view from the function you should autorelease it. Otherwise it will leak memory. So initialization should look something like this:
UIImageView *view = [[[UIImageView alloc] initWithImage:image] autorelease];
Everything else seems to look good.