I am running into an strange behavior when using NSOpeation.
I am calling a function (-createTagView) that creates an UIButton to then add it to a view.
For some reason it's not adding them. If I call the function from outside the NSOperation everything works fine.
Any ideas? Thanks.
This how I create the NSOperation (within a ViewController object)
> NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(createTagView:) object:data];
> [operationQueue addOperation:operation];
> [operation release];
And this is the function called ([Tag view] is the UIButton):
-(void) createTagView:(NSMutableArray *) data
{
NSInteger t_id = (NSInteger)[data objectAtIndex:0];
NSString *t_name = (NSString *)[data objectAtIndex:1];
NSString *t_rawname = (NSString *)[data objectAtIndex:2];
Tag *t = [[Tag alloc] initWithId:(NSInteger)t_id name:t_name rawname:t_rawname];
[self.view addSubview:[t view]];
[t release];
}
NSOperation uses a separate thread to run, you must call [addSubview: ] within the main thread. There is a method [object performSelectorOnMainThread:withObject:waitUntilDone:] - you can use it to add the subview.
[self.view performSelectorOnMainThread:#selector(addSubview:) withObject:aView waitUntilDone:YES];
Related
My Requirement is download all images in application memory and display it from local if its available.
Below is my code to access image from local and if its not available then it will download then display.
[cell.imgProfilePic processImageDataWithURLString:cData.PICTURE];
I have made custom UIImageView class
DImageView.h
#import <UIKit/UIKit.h>
#interface DImageView : UIImageView
#property (nonatomic, strong) UIActivityIndicatorView *activityView;
- (void)processImageDataWithURLString:(NSString *)urlString;
+ (UIImage *)getSavedImage :(NSString *)fileName;
#end
DImageView.m
#import "DImageView.h"
#define IMAGES_FOLDER_NAME #"DImages"
#implementation DImageView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{ }
return self;
}
- (void)dealloc
{
self.activityView = nil;
[super dealloc];
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self)
{
[self initWithFrame:[self frame]];
}
return self;
}
- (void)processImageDataWithURLString:(NSString *)urlString
{
#autoreleasepool
{
UIImage * saveImg =[DImageView getSavedImage:urlString];
if (saveImg)
{
#autoreleasepool
{
dispatch_queue_t callerQueue = dispatch_get_main_queue();
dispatch_async(callerQueue, ^{
#autoreleasepool{
[self setImage:saveImg];
}
});
}
}
else
{
[self showActivityIndicator];
NSURL *url = [NSURL URLWithString:[urlString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
dispatch_queue_t callerQueue = dispatch_get_main_queue();
dispatch_queue_t downloadQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,0);
__block NSError* error = nil;
dispatch_async(downloadQueue, ^{
#autoreleasepool
{
NSData * imageData = [NSData dataWithContentsOfURL:url options:NSDataReadingUncached error:&error];
if (!error)
{
dispatch_async(callerQueue, ^{
#autoreleasepool {
UIImage *image = [UIImage imageWithData:imageData];
[self setImage:image];
[self hideActivityIndicator];
[self saveImageWithFolderName:IMAGES_FOLDER_NAME AndFileName:urlString AndImage:imageData];
}
});
}
}
});
dispatch_release(downloadQueue);
}
}
}
- (void) showActivityIndicator
{
self.activityView = [[UIActivityIndicatorView alloc]initWithFrame:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
self.activityView.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin;
self.activityView.hidesWhenStopped = TRUE;
self.activityView.backgroundColor = [UIColor clearColor];
self.activityView.activityIndicatorViewStyle = UIActivityIndicatorViewStyleGray;
[self addSubview:self.activityView];
[self.activityView startAnimating];
}
- (void) hideActivityIndicator
{
CAAnimation *animation = [NSClassFromString(#"CATransition") animation];
[animation setValue:#"kCATransitionFade" forKey:#"type"];
animation.duration = 0.4;;
[self.layer addAnimation:animation forKey:nil];
[self.activityView stopAnimating];
[self.activityView removeFromSuperview];
for (UIView * view in self.subviews)
{
if([view isKindOfClass:[UIActivityIndicatorView class]])
[view removeFromSuperview];
}
}
- (void)saveImageWithFolderName:(NSString *)folderName AndFileName:(NSString *)fileName AndImage:(NSData *) imageData
{
#autoreleasepool{
NSFileManager *fileManger = [[NSFileManager defaultManager] autorelease];
NSString *directoryPath = [[NSString stringWithFormat:#"%#/%#",[DImageView applicationDocumentsDirectory],folderName] autorelease];
if (![fileManger fileExistsAtPath:directoryPath])
{
NSError *error = nil;
[fileManger createDirectoryAtPath:directoryPath withIntermediateDirectories:YES attributes:nil error:&error];
}
fileName = [DImageView fileNameValidate:fileName];
NSString *filePath = [[NSString stringWithFormat:#"%#/%#",directoryPath,fileName] autorelease];
BOOL isSaved = [imageData writeToFile:filePath atomically:YES];
if (!isSaved)DLog(#" ** Img Not Saved");
}
}
+ (NSString *)applicationDocumentsDirectory
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *basePath = ([paths count] > 0) ? [paths objectAtIndex:0] : nil;
return basePath;
}
+ (UIImage *)getSavedImage :(NSString *)fileName
{
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
fileName = [DImageView fileNameValidate:fileName];
NSFileManager * fileManger = [[NSFileManager defaultManager] autorelease];
NSString * directoryPath = [[NSString stringWithFormat:#"%#/%#",[DImageView applicationDocumentsDirectory],IMAGES_FOLDER_NAME] autorelease];
NSString * filePath = [[NSString stringWithFormat:#"%#/%#",directoryPath,fileName] autorelease];
if ([fileManger fileExistsAtPath:directoryPath])
{
UIImage *image = [[[UIImage imageWithContentsOfFile:filePath] retain]autorelease];
if (image)
return image;
else
return nil;
}
[pool release];
return nil;
}
+ (NSString*) fileNameValidate : (NSString*) name
{
name = [name stringByReplacingOccurrencesOfString:#"://" withString:#"##"];
name = [name stringByReplacingOccurrencesOfString:#"/" withString:#"#"];
name = [name stringByReplacingOccurrencesOfString:#"%20" withString:#""];
return name;
}
#end
Everything is working fine with smooth scrolling as well as asyncImage download in background.
The issue is when i scroll UITableview application memory is continuously increase and after some time i got Receive memory waring 2/3 time then application crash.
When i use AsyncImageView class that time memory not increase and its working fine. But due to app requirement i saved all images to Document Directory and display from it if its available.
i have tried with #autoreleasepool and release some variable but not getting success.
I appreciated if any one have the solution to manage memory management.
**ARC is off in my application.**
It's possible that UIImagePNGRepresentation returns non-autoreleased object - you can try to release it and see if that results in a crash. Obviously you are not releasing something, but nothing other than the image representation appears obvious.
A few other comments:
run your app in Instruments, using the ObjectAlloc tool, and it should be immediately obvious what objects are not dealloced. If you don't know Instruments, well, its time now to learn it.
you can 'track' objects and get a message when they are dealloced using ObjectTracker - however it was designed for ARC so you may need to tweak it. If you use it you would see a message when each of your objects are dealloced
when the table view is done with a cell, there is a delegate method that you can receive that tells you so, and you can then nil (release) and objects the cell retains
your use of downloadQueue is bizarre - create it once in your instance as an ivar, use it as you need, and in dealloc release it
you hide the activity spinner on the main queue, but don't start it on the main queue
you command the activity view to remove itself from its superview, but then look for in in the subviews and try to remove it there:
[self.activityView removeFromSuperview];
for (UIView * view in self.subviews)
{
if([view isKindOfClass:[UIActivityIndicatorView class]])
[view removeFromSuperview];
}
In the end, Instruments is what you want. You can read up more about it here, or just google and you will surely find a slew of blogs to read.
Yes Finally i have resolved it.
The code which is in Question is working fine now. but Without release some objects and #autoreleasepool block which is in code, memory was increase continuously during scroll UITableView.
From the Instrument i found that memory increase in UILableView and UIImageView. I am using Custom UITableViewCell and in that file i havnt implement dealloc method. So When i have implement dealloc method in UITableViewCell .m file and release & nil all object.
After that memory not increase during scroll TableView and its Resolved the issue.
As per my Understanding there is an issue in your "getSavedImage" Method you have to manage memory Manually instead of 'autorelease' so as My suggestion is use
UIImage *image = [[UIImage alloc] initWithContentsOfFile:filePath]
and also release it after use of it. means after '[self setImage:saveImg];'
[saveImg release]
instead of this.
[[UIImage imageWithContentsOfFile:filePath] retain];
'Don't Use Autorelease because it has staying in memory until pool not drain' and just because of this you got an memory issue.
I am playing around with NSOperationQueue in order to run some code in the background and have it update a UILabel. Here's the viewDidLoad.
- (void)viewDidLoad
{
[super viewDidLoad];
queue = [[NSOperationQueue alloc] init];
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(counterTask) object:nil];
[queue addOperation:operation];
}
And here's the method called as the invocation operation:
- (void)counterTask {
for (int i=0; i<5000000; i++) {
if (i % 100 == 0) {
[self.firstLabel performSelectorOnMainThread:#selector(setText:)
withObject:[NSString stringWithFormat:#"%d", i]
waitUntilDone:YES];
}
}
[self.firstLabel performSelectorOnMainThread:#selector(setText:) withObject:#"finished." waitUntilDone:NO];
}
As the loop counts up, and more and more #"%d" NSStrings are created, the memory usage naturally goes up. Once the loop finishes however, the memory doesn't seem to deallocate. I expected the memory to fall as the setText: message uses new instances of NSString and releases the old ones.
If I change the loop condition to i<5000000*2, the memory usage is roughly double by the end – so it's definitely something happening on each iteration causing the leak.
Why is memory leaking here?
EDIT: Forgot to mention that I'm using ARC.
ARC doesn't remove retain / release / autorelease, it just controls the calling of these methods. You can add your own autorelease pool into your loop to force cleanup as it goes:
for (int i=0; i<5000000; i++) {
if (i % 100 == 0) {
#autoreleasepool {
[self.firstLabel performSelectorOnMainThread:#selector(setText:)
withObject:[NSString stringWithFormat:#"%d", i]
waitUntilDone:YES];
}
}
}
Let's try:
- (void)counterTask {
#autoreleasepool {
for (int i=0; i<5000000; i++) {
if (i % 100 == 0) {
[self.firstLabel performSelectorOnMainThread:#selector(setText:)
withObject:[NSString stringWithFormat:#"%d", i]
waitUntilDone:YES];
}
}
}
[self.firstLabel performSelectorOnMainThread:#selector(setText:) withObject:#"finished." waitUntilDone:NO];
}
What happening in your loop that you are creating NSString and ARC add it to auto release pool.
not releases the memory(NSString*) immediately , will release later.
One more thing is that, actually performSelectorOnMainThread retains both target and objects.
So best way is that you create nsstring instance after sending it to selector set it to nil so ARC will release it.
NSString* strText=[[NSString alloc]initWithFormat:#"%d",i ];
[self.firstLabel performSelectorOnMainThread:#selector(setText:)
withObject:strText
waitUntilDone:YES];
strText=nil;
IMO, the suggested approach of #Wain should fix the issue.
But you may also use this:
- (void)counterTask {
assert([NSThread currentThread] != [NSThread mainThread]);
for (int i=0; i<5000000; i++) {
if (i % 100 == 0) {
dispatch_sync(dispatch_get_main_queue(), ^{
#autoreleasepool {
self.firstLabel.text = [NSString stringWithFormat:#"%d", i];
}
});
}
}
dispatch_async(dispatch_get_main_queue, ^{
self.firstLabel.text = #"finished";
});
}
Try this
(void)counterTask {
__weak NSString *str;
for (int i=0; i<50000; i++) {
if (i % 100 == 0) {
str = [NSString stringWithFormat:#"%d", i];
[self.logInTxtField performSelectorOnMainThread:#selector(setText:)
withObject:str
waitUntilDone:YES];
}
}
[self.logInTxtField performSelectorOnMainThread:#selector(setText:) withObject:#"finished." waitUntilDone:NO];
}
I'm a noobie in the Objective-C language, and I have a little problem.
In fact, I have 2 TableViews, and when I go from one to the other I parse some XML from the internet. The parsing is doing well, but I wanted to add an UIActivityIndicatorView between those 2 views to tell to the user that something is loading.
So, to do that, I wanted to do the parsing in another thread and show the UIActivityIndicatorView in the main thread. So here's my code :
- (void)viewDidLoad
{
[super viewDidLoad];
UIActivityIndicatorView *activityIndicator;
activityIndicator = [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
activityIndicator.frame = CGRectMake(0.0, 0.0, 40.0, 40.0);
activityIndicator.center = self.view.center;
[self.view addSubview: activityIndicator];
activityIndicator.startAnimating;
dispatch_queue_t queue = dispatch_get_global_queue(0,0);
dispatch_async(queue, ^{
NSError *error = nil;
// we will put parsed data in an a array
titles = [[NSMutableArray alloc] init];
urls = [[NSMutableArray alloc] init];
CXMLDocument *rssParser = [[CXMLDocument alloc] initWithContentsOfURL:[NSURL URLWithString:_emissionSelectionnee] options:0 error:&error];
NSArray *nodes = NULL;
nodes = [rssParser nodesForXPath:#"//rss/channel/item/title" error:nil];
for (CXMLElement *title in nodes) {
[titles addObject:[title stringValue]];
}
nodes = NULL;
nodes = [rssParser nodesForXPath:#"//rss/channel/item/enclosure" error:nil];
for (CXMLElement *url in nodes) {
[urls addObject:[[url attributeForName:#"url"] stringValue]];
}
dispatch_sync(dispatch_get_main_queue(), ^{
activityIndicator.stopAnimating;
});
}
}
So now, the UIActivityIndicator shows up, but the cells are empty.. When I do not use the dispatch_queue_t, it works well..
Does someone have an idea?
Thank you in advance!
You need to reload your Table view (in the same block where you hide the activity indicator):
[self.tableView reloadData]
I'm trying to use background threds to do some computations on an iPad.
The thing is even thou the computationa are running. The UI is blocked while they run...
What am I doing wrong.
[mc evaluateFormula:adapted runNo:10000];
This is called from an IBAction.
This is the code that is called:
-(void)evaluateFormula:(NSDictionary *)frm runNo:(NSUInteger)runCount
{
self.runCount = runCount;
self.frm = frm;
[self performSelectorInBackground:#selector(backgroundEvalFrm) withObject:nil];
// for (int i = 0; i < runCount; i++) {
// [self runFormula:frm];
// }
//
}
-(void)backgroundEvalFrm
{
percentVal = self.runCount / 100;
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue setMaxConcurrentOperationCount:20];
for (int i = 0; i<self.runCount; i++) {
NSInvocationOperation *op =[[NSInvocationOperation alloc] initWithTarget:self selector:#selector(runFormula:) object:self.frm];
[queue addOperation:op];
}
}
So why is the UI blocked?
Here is the thread return code... it's all in the same class
-(void)runFormula:(NSDictionary *)frm
{
NSMutableString *formula = [[frm objectForKey:kFormulaExpresion] mutableCopy];
NSArray *variables = [frm objectForKey:kVariableArray];
NSArray *evals = [self evaluateVariables:variables];
for (NSDictionary *var in evals) {
NSString *sym = [var objectForKey:kVariableSymbol];
[formula replaceOccurrencesOfString:sym withString:[[var objectForKey:#"numVal"] stringValue] options:NSCaseInsensitiveSearch range:NSMakeRange(0, [formula length])];
}
//parse formula
//NSLog(#"formula to parse:%#",formula);
NSNumber *resNo = [formula numberByEvaluatingString];
// NSLog(#"formula %# the result : %f",formula,[resNo doubleValue]);
//NSNumber *resNo = [NSNumber numberWithDouble:result];
[self performSelectorOnMainThread:#selector(addNewResult:) withObject:resNo waitUntilDone:NO];
}
#pragma mark -- data aggregation delegate
-(void)addNewResult:(NSNumber *)nr
{
NSLog(#"index : %i result: %f",currentIndex,[nr doubleValue]);
[[self delegate] didReceiveResult:nr];
resultsArray[currentIndex]=[nr doubleValue];
currentIndex ++;
if ( (currentIndex % percentVal) == 0) {
[[self delegate] percentCompleted];
}
}
if your calculations are all competing for the same resource (i.e. CPU or I/O) in an uncoordinated manner (highly probable), then you should significantly lower the maximum concurrent operation count -- try 2. chances are, they will complete using less time/energy. furthermore, the main thread will not be reduced to less than 5% of the CPU time during the period that the calculations are executing (result: more responsive UI).
I'm trying to get the performSelector to load the activity indicator on a separate thread while the web service call is made. The issue is the "return parsedData;" is not being set in fetchJSON:. However, when I print the parsedData in getData: method, it's coming back fine. I assume the return is being executed before the performSelector is finished getting the data. Is there any way to have the fetchJSON: method wait for performSelector to finish before returning parsedData?
-(void)showActivityIndicator
{
CGRect frame = CGRectMake(0.0, 0.0, 125.0, 125.0);
loading = [[UIActivityIndicatorView alloc] initWithFrame:frame];
[loading setActivityIndicatorViewStyle:UIActivityIndicatorViewStyleWhiteLarge];
[loading hidesWhenStopped];
//loading.center=[self tableView].center;
[loading startAnimating];
[loading sizeToFit];
loading.autoresizingMask = (UIViewAutoresizingFlexibleLeftMargin |
UIViewAutoresizingFlexibleRightMargin |
UIViewAutoresizingFlexibleTopMargin |
UIViewAutoresizingFlexibleBottomMargin);
// initing the bar button
//UIBarButtonItem *loadingView = [[UIBarButtonItem alloc] initWithCustomView:loading];
//loadingView.target = self;
[loadingView addSubview:loading];
}
- (NSDictionary *)fetchJSON:(NSString *)urlString
{
NSMutableString *domain = [[NSMutableString alloc] initWithString:#"http://www.blablabla.com/dev/"];
[domain appendString:urlString];
//NSLog(#"%#", domain);
NSURL *url = [NSURL URLWithString:domain];
NSURLRequest *req = [NSURLRequest requestWithURL:url];
[self showActivityIndicator];
[self performSelector:#selector(getData:) withObject:req afterDelay:0.0];
//[self performSelectorOnMainThread:#selector(getData:) withObject:req waitUntilDone:YES];
return parsedData;
}
-(IBAction)getData:(id)sender
{
NSURLResponse* response = nil;
NSData *data = [NSURLConnection sendSynchronousRequest:sender returningResponse:&response error:nil];
parsedData = [NSJSONSerialization
JSONObjectWithData:data
options:NSJSONReadingMutableLeaves
error:nil];
NSLog(#"GET DATA %#", parsedData);
[loading stopAnimating];
loading = nil;
}
"performSelector to load the activity indicator on a separate thread"
??
UIKit is, in general, not thread safe.
Between the commented out code and confusing method declarations (why is getData: an IBAction? Is it called both from within code and as a control's action?), I'm really missing context on what you are trying to do here.
Could you start by just giving a high-level overview of what you're trying to accomplish and how this code fits into that picture?