Objective C threads, setting image in iOS tableViewCell - iphone

I've just started using threads and I'm trying to get better performance from my TableView in my app. Each tableViewCell has an imageView and the image is loaded from disk when the tableViewCell is created. I want to load the Image on a differant thread, then set the UIImageView on the main thread. My question is, can a method that is being ran on another thread return a value to the main thread? Is there a better approach for doin this?
Thanks for any help in advance.

maybe something like this, assuming your icons are in the document's directory:
#define DOCUMENTS_DIRECTORY [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]
//inside - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
NSDictionary *d = [NSDictionary dictionaryWithObjectsAndKeys:indexPath, #"indexPath", #"image1.png", #"imageName", nil];
[NSThread detachNewThreadSelector:#selector(loadIcon:) toTarget:self withObject:d];
//
- (void)iconLoaded:(NSDictionary*)dict {
[icons replaceObjectAtIndex:[[dict objectForKey:#"index"] intValue] withObject:[UIImage imageWithData:[dict objectForKey:#"imageData"]]];
[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:[dict objectForKey:#"indexPath"]]];
}
- (void)loadIcon:(NSDictionary*)dict {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSString *filePath = [DOCUMENTS_DIRECTORY stringByAppendingPathComponent:[NSString stringWithFormat:#"%#.jpg", [dict objectForKey:#"imageName"]]];
NSData *imageData = [NSData dataWithContentsOfFile:filePath];
[self performSelectorOnMainThread:#selector(iconLoaded:) withObject:[NSDictionary dictionaryWithObjectsAndKeys:[dict objectForKey:#"indexPath"], "indexPath", imageData, #"imageData", nil] waitUntilDone:YES];
[pool drain];
}
you will need to keep track of which cells you are loading an image for, so you dont try to load one while it is already loading it. there may be some small syntax errors as i did not compile this, just wrote it freehand.
icons is an NSMutableArray holding a UIImage for each cell

Try this:
https://github.com/foursquare/fully-loaded

Yes, you can pass data to the main thread. See the following method in NSObject API docs:
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait
You get to pass a single object reference in the arg parameter.

Related

Multiple NSThreads running simultaneously causing app freezes

I am developing an application in which I have a display a lot of images in my table view.These images has to come from server, so I have create another thread in which the image get processed and then set on table view cell to make our table view scrolls smoothly and properly.
My problem is when I scrolls my table view to a number of times, the app get freezes and after some time the xcode shows the message shown in below image:-
My table view cell code :-
id object = [imageDictFunctionApp objectForKey:[NSString stringWithFormat:#"%d",functionAppButton.tag]];
if(!object){
NSArray *catdictObject=[NSArray arrayWithObjects:[NSNumber numberWithInt:functionAppButton.tag],indexPath,[NSNumber numberWithInt:i],nil];
NSArray *catdictKey=[NSArray arrayWithObjects:#"btn",#"indexPath",#"no",nil];
NSDictionary *catPassdict=[NSDictionary dictionaryWithObjects:catdictObject forKeys:catdictKey];
[NSThread detachNewThreadSelector:#selector(displayingSmallImageForFunctionsApps:) toTarget:self withObject:catPassdict];
}
else
{
if(![object isKindOfClass:[NSNull class]]){
UIImage *img = (UIImage *)object;
[functionAppButton setImage:img forState:UIControlStateNormal];
}
-(void)displayingSmallImageForFunctionsApps:(NSDictionary *)dict
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSIndexPath *path=[dict objectForKey:#"indexPath"];
NSArray *catArray=[self.functionDataDictionary objectForKey:[self.functionsKeysArray objectAtIndex:path.row]];
int vlaue=[[dict objectForKey:#"no"] intValue];
NSDictionary *dict1=[catArray objectAtIndex:vlaue];
NSString *urlString=[dict1 objectForKey:#"artwork_url_large"];
NSURL *url = [NSURL URLWithString:urlString];
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]];
if(image){
[imageDictFunctionApp setObject:image forKey:[NSString stringWithFormat:#"%d",[[dict objectForKey:#"btn"] intValue]]];
NSMutableDictionary *dict2=[NSMutableDictionary dictionaryWithCapacity:4];
[dict2 setObject:image forKey:#"imageValue"];
[dict2 setObject:[dict objectForKey:#"btn"] forKey:#"tag"];
[dict2 setObject:[dict objectForKey:#"indexPath"] forKey:#"indexPath"];
[self performSelectorOnMainThread:#selector(setImageInCellDictCategroryTable:) withObject:dict2 waitUntilDone:NO];
}
else{
[imageDictFunctionApp setObject:[NSNull null] forKey:[NSString stringWithFormat:#"%d",[[dict objectForKey:#"btn"] intValue]]];
}
[pool drain];
}
- (void)setImageInCellDictCategroryTable:(NSDictionary *)dict{
UITableViewCell *myCell = (UITableViewCell *)[categoriesAndFunctionsTableView cellForRowAtIndexPath:[dict objectForKey:#"indexPath"]];
UIButton *myBtn = (CustomUIButton *)[myCell viewWithTag:[[dict objectForKey:#"tag"] intValue]];
if ([dict objectForKey:#"imageValue"]) {
[myBtn setImage:[dict objectForKey:#"imageValue"] forState:UIControlStateNormal];
}
}
I have posted all my code that might be linked with this error.Please anyone suggest me how to solve this issue.
Thanks in advance!
I would suggest not to use threads and move you code to GCD, looks like what you want to use is a serial queue.
So what I would guess is happening is that you are running out of Mach ports. It looks to me like you are starting a thread for every single cell in your table and then they are all trying to schedule tasks to run on the main runloop when they are done. This is going to stress your system.
I would create an NSOperation for each image and schedule them all on the same NSOperationQueue. The runtime will use a pool of threads tuned to the specific system to run all of the operations.
For a simple thing like this, you can also use GCD as Oscar says, but I recently read on the Apple list that NSOperationQueue is preferred because it is higher level. It gives you more options for controlling what happens to your background tasks.

Table View Scrolling cause problems when I use dynamic data

When I use static data there is no problem but I use dynamic data with Web services there is problem (table view scrolling cause crash program) why? If I comment these lines add static data it works;
//tempCs is NSDictionary
tempDc = [arrHaberler objectAtIndex:indexPath.row];
cell.textLabel.text = [tempDc valueForKey:#"short_header"];
NSData *imgData = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:[tempDc valueForKey:#"iphone_src"]]];
UIImage *myImage = [[[UIImage alloc] initWithData:imgData] autorelease];
cell.imageView.image = myImage;
You don't release the imgData. You'd want to do that after creating the UIImage.
Other than that, from your description, maybe the numbersOfRowsInSection method has an error?
EDIT (after discussion):
(crash due to unrecognized selector (ie method from NSArray) sent to instance of NSString)
There are many ways you can come to this state, including accessing some memory that was released and reused (ie missing a retain), or overwriting an array with a string due to some parsing yielding a wrong result.
I try something with made an comment another lines and problem is on this line:
tempDc = [arrHaberler objectAtIndex:indexPath.row];
I change "indexPath.row" to "0" still cause crash... Problem about when is assign data to NSDictionary
There is no null-checking (setting NSData *imgData and setting UIImage *myImage) and there in a synchronous call to server. fmpov, problem is out there.
#VNevzatR i am just asking you that Are you are calling plenty no of images from somewhere....because this is no the problem you UITableView as you said working fine in static data,the problem is some where else, so if you are calling plenty of images at a time....Try doing this way...release that pool.
-(void) parseImages
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
//Fetch your images here
[self performSelectorOnMainThread:#selector(Done) withObject:nil waitUntilDone:YES];
[pool release];
}
-(void) Done {
[self.tableView reloadData];
}
Hope it will resolve your problem...Good Luck

iPhone: Can't set object received from thread in NSMutableDictionary

I've got a thread (specifically an NSOperation) that runs to load some images for me for a scroll view when my main view asks for them. Any number of these NSOperations can be queued at once. So it goes with the filepath I give it and loads the image from the disk (as UIImages) and then sends the object back to my mainview by using performSelectorOnMainThread: and passing my mainview an NSDictionary of the object, and an image ID value. My main view is then supposed to insert the image object and the image ID string into an NSMutableDictionary that it has for the mainview to be able to use. I've verified that the NSMutableDictionary is allocated and initialized fine, but when the method the NSOperation calls tries to add the objects to the dictionary nothing happens. I've verified that the object and string i get from the dictionary the thread sent me are not null or anything but yet it doesn't work. Am I not doing something right or using a bad technique? What would anyone suggest to do in a situation like this where I need to add UIImages to an NSMutableDictionary from a thread? Thanks so much!
Here's the NSOperation code I use:
- (void)main {
NSString *filePath = [applicaitonAPI getFilePathForCachedImageWithID:imageID andSize:imageSize];
UIImage *returnImage = [[UIImage alloc] initWithContentsOfFile:filePath];
if (returnImage) {
NSMutableDictionary *dict = [[NSMutableDictionary alloc] initWithCapacity:2];
[dict setObject:returnImage forKey:#"IMAGE"];
[dict setValue:[NSString stringWithFormat:#"%d", imageID] forKey:#"IMAGE_ID"];
NSDictionary *returnDict = [[NSDictionary alloc] initWithDictionary:dict];
[dict release];
[mainViewController performSelectorOnMainThread:#selector(imageLoaderLoadedImage:) withObject:returnDict waitUntilDone:NO];
[returnDict release];
}
}
And here's the method on the main thread:
- (void)imageLoaderLoadedImage:(NSDictionary *)dict {
UIImage *loadedImage = [dict objectForKey:#"IMAGE"];
NSString *loadedImage = [dict valueForKey:#"IMAGE_ID"];
[imagesInMemoryDictionary setObject:loadedImage forKey:loadedImageID];
[self drawItemsToScrollView];
}
[mainViewController performSelectorOnMainThread:#selector(imageLoaderLoadedImage:) withObject:nil waitUntilDone:NO];
You're not passing returnDict as the parameter to the method. You're passing nil.
A couple of other thoughts:
you don't need to create returnDict. You can just use dict as the method parameter.
you're leaking returnImage.
edit
Since you apparently are passing returnDict as the parameter to the method, my other guess would be that mainViewController is nil. Other than that, your code looks functional.

Iphone: threading issue?

I am initialing a dictionary in viewDidLoad and using it to create table view cells.
The first load works fine. But as soon as I scroll the table view to see the item (not displayed at the bottom) the app crashes. Through the Debugger I noticed the address of the dictionary item "rootItemsDict" changes when I did the scroll. Not able to figure out why that is. From my understanding the address of an object initialized once should remain same, at least within the given class instance. Any thoughts?
- (void)viewDidLoad {
[super viewDidLoad];
NSString *path = [[NSBundle mainBundle] pathForResource:#"menu" ofType:#"plist"];
rootItemsDict = [NSDictionary dictionaryWithContentsOfFile:path];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *title = (NSString *)[[[rootItemsDict objectForKey:#"rootMenuItems"] objectAtIndex:row] objectForKey:#"heading"];
+dictionaryWithContentsOfFile: returns an autoreleased instance. To take ownership you need to explicitly retain it:
rootItemsDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
... or use the alloc/init form:
rootItemsDict = [[NSDictionary alloc] initWithContentsOfFile:path];
... or if you have a suitable property declaration (retain) use the setter:
self.rootItemsDict = [NSDictionary dictionaryWithContentsOfFile:path];
I recommend to read the Memory Management Programming Guide, especially the section on object ownership.

release NSMutable array in obj-c

where to dealloc/ release my NS-mutable array a1 ??
see this
- (void)viewDidLoad {
[NSThread detachNewThreadSelector:#selector(loadImage) toTarget:self withObject:nil];
}
- (void) loadImage
{
NSLog(#" THREAD METHOD");
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSUserDefaults *imgg = [NSUserDefaults standardUserDefaults];
myimg= [imgg stringForKey:#"keyToimg"];
NSLog(#"RES image sssssssss is = %#",myimg);
a1 = [[NSMutableArray alloc] init];
[a1 addObjectsFromArray:[myimg componentsSeparatedByString:#"\n\t"]];
//[a1 removeAllObjects];
////
//[myimg release];
[pool release];
}
and in table cell of secition 3 i am displaying image
switch(indexPath.section)
{
NSString *urlE=[a1 objectAtIndex:1];
NSLog(#"url is %#",urlE);
NSData *backgroundData = [NSData dataWithContentsOfURL:[NSURL URLWithString:urlE]];
image = [UIImage imageWithData:backgroundData];
myImageView= [[UIImageView alloc] initWithImage:image];
[myImageView setUserInteractionEnabled:YES];
CGRect rect=CGRectMake(20 ,10, 270, 180);
myImageView.frame = rect;
myImageView.tag = i;
[cell.contentView addSubview:myImageView];
}
and based on tap images are changing
pragma mark working image tap
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#" life count %d",[myimg retainCount]);
NSLog(#" life array count %d",[a1 retainCount]);
//NSLog(#" GITSHffffffffffffffffffffffffffffffC");
NSUInteger sections = [indexPath section];
//NSLog(#"row is %d",sections);
if (sections == 3)
{ //Its either 1 or 0 I don't remember, it's been a while since I did some tableview
if(tap<[a1 count]-1) {
NSLog(#" life array count %d",[a1 retainCount]);
tap++;
NSString *sa=[a1 objectAtIndex:tap];
//////////////////////
image= [[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:[NSString stringWithFormat: sa,[a1 objectAtIndex:tap ]]]]];
NSData *imageData = [NSData dataWithData:UIImageJPEGRepresentation(image, 1.0)];
myImageView.image = image;
//[myimg release];
//[a1 release];
}
else {
tap=1;
//[myimg release];
//[a1 release];
}
}
//[a1 release];
}
so where should i release my a1 and myimg
a1 will never be released using this code.
You should put it on a member variable or add autorelease after init.
By the way, your myImageView should be released after you add it to the cell view.
It is possible because of the retain/release logic: when you alloc the myImageView the retain count is +1, once you add it to cell view,it is now +2, you should then release it so that the retain comes back to +1 and then when cell view will be further deallocated, it will decrement the retain count to 0.
The same logic for the variable image in the last function
Regards
Meir assayag
Instead of :
a1 = [[NSMutableArray alloc] init];
[a1 addObjectsFromArray:[myimg componentsSeparatedByString:#"\n\t"]];
Consider:
a1 = [NSMutableArray arrayWithArray:[myimg componentsSeparatedByString:#"\n\t"]];
That'll initialize your a1 with an autoreleased NSMutableArray object, and then you don't have to worry about manually releasing it.
The thing I don't know is whether your [pool release] will release it, but... I'd really prefer you NOT put that business in a background thread, but rather use asynchronous network methods to get your image data.
By the way, as I was learning iPhone development, I went through three or four levels of "aha moments" about backgrounded networking. One of them had to do with running selectors on background threads. That lasted about a week until I discovered ASIHttpRequest, which is how I do it now. MUCH simpler way to put network interactions in the background without having to mess with threading or any of that nonsense. See http://allseeing-i.com/ASIHTTPRequest/
If you look at my answers, every time HTTP client networking comes up I recommend ASI. I really don't mean to be a shill for it--it's just made my life so much easier I think everyone needs to know about it.