I'm trying to write some generic http response handler functions that sometimes open UIAlertViews.
These generic handlers are class methods don't have knowledge of their callers (at the moment).
But I'm facing an obvious problem about how to alloc/dealloc the UiAlertView delegate object.
e.g.
MyAlertViewHandler* alertHandler = [[MyAlertViewHandler alloc] init];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:msg message:nil delegate:alertHandler cancelButtonTitle:TEXT_NEVERMIND otherButtonTitles:TEXT_RESET_PASSWORD,nil];
[alert show];
[alert autorelease];
There's an obvious memory leak there cos I'm alloc'ing and not release it anywhere.
So, where do I "hang" MyAlertViewHandler such that I can release it to avoid the memory leak?
One idea is to tell the caller there's a NSObject that needs to be released and give responsibility back to the UIViewController - but is there another way?
I hope my question is clear enough.
In your alertView:didDismissWithButtonIndex: delegate method (in your MyAlertViewHandler) you could simply [self release]; or [self autorelease]; as the very last command.
Related
How can I wait for a certain task to be done?
This is what I'm trying to do:
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:stringURL]];
if (!data) {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:[NSString stringWithFormat:#"%#", NOCONNECTIONALERTTITLE] message:[NSString stringWithFormat:#"%#", NOCONNECTIONALERTMESSAGE] delegate:self cancelButtonTitle:[NSString stringWithFormat:#"%#", NOCONNECTIONALERTCANCELBUTTON] otherButtonTitles:nil, nil];
[alert show];
} else { ... }
This if is always false, since its called before data even is initialized. I'm 100% sure, that the connection is established.
I want to use this as an easy alternative to checking the internet-connection programmatically.
dataWithContentsOfURL: is a blocking call, so data is initialised when you try to use it. If it's nil then you likely have an issue with the URL you're trying to load.
Because this is blocking you shouldn't use it on the main thread. And, generally, you shouldn't want to wait for completion - you should embrace the asynchronous nature of network operations and use appropriate asynchronous API and designs to handle them.
Log the NSURL that you are creating to check it exists (and fix if it doesn't).
Your simplest option for running an asynchronous download and handling the result is to use NSURLConnection +sendAsynchronousRequest:queue:completionHandler:. Supply the queue as [NSOperationQueue mainQueue] so that your callback is run on the main thread and you can update your UI.
REVISED...
The crux of the app is communicating with a database server. Responses from the server to the app are all in XML. There are several screens. Example, screen 1 lists the user's information, screen 2 lists the user's past trades, allows new trades, and so on.
Here is some code from my AppDelegate:
StartViewController *svc = [[StartViewController alloc] init];
TradeViewController *tvc = [[TradeViewController alloc] init];
CashViewController *cvc = [[CashViewController alloc] init];
ComViewController *covc = [[ComViewController alloc] init];
PrefsViewController *pvc = [[PrefsViewController alloc] init];
NSMutableArray *tabBarViewControllers = [[NSMutableArray alloc] initWithCapacity:5];
UITabBarController *tabBarController = [[UITabBarController alloc] init];
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:svc];
[tabBarViewControllers addObject:navigationController];
navigationController = nil;
navigationController = [[UINavigationController alloc] initWithRootViewController:tvc];
[tabBarViewControllers addObject:navigationController];
navigationController = nil;
navigationController = [[UINavigationController alloc] initWithRootViewController:cvc];
[tabBarViewControllers addObject:navigationController];
navigationController = nil;
navigationController = [[UINavigationController alloc] initWithRootViewController:covc];
[tabBarViewControllers addObject:navigationController];
navigationController = nil;
navigationController = [[UINavigationController alloc] initWithRootViewController:pvc];
[tabBarViewControllers addObject:navigationController];
navigationController = nil;
[tabBarController setViewControllers:tabBarViewControllers];
[[self window] setRootViewController:tabBarController];
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
Trying to stick with the MVC style, I have a singleton class which does all of the "processing".
Now an example on how I run into a wall… the user can change their email address on screen 5. Enter new email address into text field and click the save button. The button then calls a method from the singleton class which sends the new email address to the server and (via the URL) and receives a XML response confirming the change.
Here are my problems:
1. I start the spinner from the view controller before I make the singleton class method call - but not knowing when the app to server send/receive is finished, how do I make the spinner stop at the right time? I can't of it from the singleton class, I tried that. From what I know, it has to be from within the VC or is there a way to change VC output from my singleton class?
The singleton class NSURLConnection is handling ALL of my communication. Everything from a simple, email change all the way to updating transaction tables. This just seems wrong to me and makes it very difficult to keep track on who is calling what. Again, I am going by my interpretation of MVC. I think it would be much easier to have a NSURLConnection for every VC and do some processing in those classes. However that would not be MVC(ish).
I have close to a 100 variables, arrays, etc… in my singleton class which I use to assign values to all my VC. This also seems wrong to me but I can't think of any other way.
how can I distinguish in the NSURLConnection delegate
(connectionDidFinishLoading) which URL call is being made?
Each of the delegate methods (such as -connectionDidFinishLoading:) has a connection parameter that tells you which connection sent the message. A given connection can only load one URL at a time, so there's a one to one correspondence between URLs and connections.
How can I tell outside of "connectionDidFinishLoading" when the download is completed?
That method tells you when the connection is finished. It's up to you to store that information somewhere where it's useful to your app.
Update: Based on what you've added, your "processing" class is your app's model. The rest of the app shouldn't care that each transaction involves a message to the server -- that's the model's business alone. Also, there's no reason that the model has to be a single object (let alone a singleton) -- it can be a group of objects that work together.
So, you might have a class (let's call it Processor) that represents the application's interface to the model (some might even call this a "model controller"). An instance of Processor might create a local database for storing the current local state of the app.You might also have a Transaction class that represents a single transaction with the server. A transaction could create a request, send it to the server, get the response, update the database, and tell the Processor that the transaction is done. Or, maybe when some other part of the app (like one of your view controllers) asks the Processor to process a new transaction, the Processor passes the requesting object along to the transaction that it creates so that the transaction can update the requestor directly.
It's hard to say what the best plan for your app is without knowing where you're planning on taking it, but the usual guidelines hold:
break your problem into parts that are easier to solve
limit the scope of each class's responsibilities
if something seems to complicated, it probably is
Breaking your model up into several classes will make it easier to test, as well. You can imagine how easy it would be to write a set of unit tests for the Transaction class. The same goes for Processor -- if the server transaction stuff is in a different class, it's easier to test that the Processor is doing the right thing.
If you have multiple NSURLConnections for the same delegate, consider using a global (well, let's say rather an instance variable) NSMutableDictionary instance, in which you store the data depending on which NSURLConnection is being called. You can use, for example, the in-memory address of the connections converted to an NSString (something like
[NSString stringWithFormat:#"%p", connection]
should do the trick).
Also, in the connectionDidFinishLoading: and connection:didFailLoadWithError: methods, remove the keys corresponding to the NSURLConnections. Thus, you can tell it from 'outside' if a connection is finished: just check if it is in the dictionary or not.
If you're downloading any data over a network connection, I would suggest using ASIHttpRequest. This will allow you to download files asynchronously, meaning your interface doesn't freeze during the download process.
If you use ASIHttpRequest, you can also set the didFinishSelector. By doing this, you can control which method is called when a specific URL has finished loading.
Have a look at this:
NSURL *url = [NSURL URLWithString:#"http://allseeing-i.com"];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setDelegate:self];
[request startAsynchronous];
[request setDidFinishSelector:#selector(requestDone:)];
Then:
- (void)requestDone:(ASIHTTPRequest *)request
{
// Use when fetching text data
NSString *responseString = [request responseString];
// Use when fetching binary data
NSData *responseData = [request responseData];
// If you want, you can get the url of the request like this
NSURL *url = [request url];
}
As for the second part of your question, if the requestDone: method has not been called, you know the download has not completed.
If you want to do something more complicated with multiple downloads, ASIHttpRequest offers queue functionality too. Take a look here.
Hope this will help you.
- (void)connectionDidFinishLoading:(NSURLConnection*)connection
{
NSString *urlString = [[[connection originalRequest] URL] absoluteString];
if ([urlString caseInsensitiveCompare:#"http://www.apple.com"] == NSOrderedSame) {
//Do Task#1
}
else if ([urlString caseInsensitiveCompare:#"http://www.google.com"] == NSOrderedSame)
{
//Do Task#2
}
}
I would recommend subclassing NSURLConnection. Simply add two properties: an NSInteger, tag, and a BOOL, isFinished. This way, you can #define tags for each different request and then identify them by tag in your delegate methods. In connectionDidFinishLoading, you can set the isFinished BOOL to YES, and then you can check in other methods if then connection is finished.
Here's my own NSURLConnection subclass, TTURLConnection:
TTURLConnection.h:
#import <Foundation/Foundation.h>
#interface TTURLConnection : NSURLConnection <NSURLConnectionDelegate>
#property (nonatomic) NSInteger tag;
#property (nonatomic) BOOL isLocked;
- (id)initWithRequest:(NSURLRequest *)request delegate:(id)delegate startImmediately:
(BOOL)startImmediately tag:(NSInteger)tagParam;
#end
TTURLConnection.m:
#import "TTURLConnection.h"
#implementation TTURLConnection
#synthesize tag;
- (id)initWithRequest:(NSURLRequest *)request delegate:(id)delegate startImmediately:
(BOOL)startImmediately tag:(NSInteger)tagParam {
self = [super initWithRequest:request delegate:delegate
startImmediately:startImmediately];
if(self) {
self.tag = tagParam;
}
return self;
}
#end
I have a NSOperation subclass,this is the main method:
(void)main
{
NSAutoreleasePool *Pool = [[NSAutoreleasePool alloc] init];
managedObjectContext = [NSManagedObjectContext contextThatNotifiesDefaultContextOnMainThread];
Message *message = (Message *) [managedObjectContext objectWithID:self.messageID];
message.status = [NSNumber numberWithInt:SKMessageSendStateStart];
[message save];
[self send];
[self finish];
[Pool drain];
}
I define the fetchResultController and defaultContext like this:
(BOOL)application:(UIApplication *)applicationdidFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[MagicalRecordHelpers setupCoreDataStackWithStoreNamed:#"Shark"];
self.context = [NSManagedObjectContext context];
[NSManagedObjectContext setDefaultContext:self.context];
self.fetchController = [Message fetchRequestAllGroupedBy:nil withPredicate:nil sortedBy:#"text" ascending:YES];
[self.fetchController setDelegate:self];
[self.fetchController performFetch:nil];
}
Everytime i call [message save],the console logout:
-NSManagedObjectContext(MagicalRecord) mergeChangesFromNotification: Merging changes to * DEFAULT
context on Main Thread *
But the NSFetchedResultsControllerDelegate never get called!
Does this mean that I set the FetchedResultsController wrong or what? I
am totally confused.
Thanks in advance.
The reason this isn't working is because MagicalRecord will automatically call performFetch: for you, thus not allowing you to set the delegate ahead of time.
Also, in your applicationDidFinishLaunching: method, you want to delete these lines:
self.context = [NSManagedObjectContext context];
[NSManagedObjectContext setDefaultContext:self.context];
You want to NOT change the default context in this case. MagicalRecord is handling stuff for you when you call setupCoreDataStackWithStoreNamed:...that is, a MOC is already available for use when that method completes, there is no need to toss the one it created for you and set the default context to a new instance in this particular case.
It's also not necessary to hold on to the context if all you're doing is going to use it to pass to one of the fetch methods provided by MagicalRecord. MagicalRecord will create a single context for its use (the 'default' context) and just use that...
I misunderstood what the [NSManagedObjectContext context] means. It create a new context in the main thread. Since the context that the fetchResultController monitor is not the same context that the change merged to, the NSFetchedResultsControllerDelegate will not get called.
Change [NSManagedObjectContext context] to [NSManagedObjectContext defaultContext] solved the problem.
I want to replace the title and message with the return values from a different class and also put it into an if statement that will see if the return value matches a value i state. can you help me out a little bit, this is my code below, I'm using theos by the way
the 3 methods i want to get the values for are
-(id)title
-(id)message
-(id)_appName
from the SBBulletinBannerItem class
any help is appreciated
%hook SBBulletinBannerController
- (void)_handleBannerTapGesture:(id)reply
{
reply = [[UIAlertView alloc] initWithTitle:#"title" message:#"message" delegate:self cancelButtonTitle:#"Close" otherButtonTitles:#"Reply", nil];
[reply show];
[reply release];
}
%end
It sounds like you'll want to use delegates and protocols. You may have seen this using standard Cocoa and Cocoa Touch classes. This is how you respond to events in a UITableView.
You can find examples of implementing this yourself here.
I have a memory leak in my iPhone app. I added Google AdMob to my app using sample code downloaded from Google. However, I was having a hard time getting it into testing mode so I added an extra variable as follows:
GADRequest *r = [[GADRequest alloc] init];
r.testing = YES;
[bannerView_ loadRequest:r];
I found the memory leak using Instruments. Instruments does not lead me to this line of code, it just dumps me in the main.m file. However, when I comment out the code relating to AdMob the leak goes away and I know enough to see that I haven't taken care of releasing this new variable. I just don't know exactly how I should set about releasing it. The variable r is not addressed in the header file so this is all the code that deals with it.
I tried adding:
- (void)dealloc {
[r release];
....
}
but that caused a build error saying "'r' undeclared". That's weird because it looks to me like I'm declaring r in the first quoted line above but I guess that's wrong. Any help would be much appreciated. I really have tried to educate myself about memory leaks but I still find them very confusing.
Just add [r release]; right below the code:
GADRequest *r = [[GADRequest alloc] init];
r.testing = YES;
[bannerView_ loadRequest:r];
[r release];
The variable r is declared only in that section of your code, so that's where it should be released. The point of releasing is to get rid of it as soon as you no longer need it, so the above should work perfectly for you.
If your r is locally declared (as it seems, judging from your snippet), then it cannot be accessed from outside its scope (here: the method it was declared in).
You either need to make it accessible within your class instance by declaring it an ivar.
Declaring it an ivar would look like this:
#interface YourClass : SuperClass {
GADRequest *request;
}
//...
#end
You then change your code to this:
request = [[GADRequest alloc] init];
request.testing = YES;
[bannerView_ loadRequest:request];
Also don't forget to release it in dealloc:
- (void)dealloc {
[request release];
//...
}
However this is not what you want in this situation (I've just included it to clarify why you get the warning about r not being declared).
You (most likely) won't need request any second time after your snippet has run, thus storing it in an ivar will only needlessly occupy RAM and add unwantd complexity to your class. Stuff you only need at the immediate time after its creation should be taken care of (released) accordingly, that is within the very same scope.
What you'll actually want to do is simply (auto)release it, properly taking care of it.
Keep in mind though that your loadRequest: will need to take care of retaining r for as long as it needs it. Apple's implementation does this, of course. But you might want to write a similar method on your own one day, so keep this in mind then.
GADRequest *r = [[GADRequest alloc] init];
r.testing = YES;
[bannerView_ loadRequest:r];
[r release]; //or: [r autorelease];
OP here. Thanks for all the detailed and thoughtful responses. This has definitely helped get a better handle on memory management. I did exactly as recommended, adding [r release]; right below the posted code. However, I still have a leak. I have isolated it to a single line of code. The following leaks:
GADRequest *r = [[GADRequest alloc] init];
r.testing = YES;
[bannerView_ loadRequest:r];
[r release];
The following does not leak:
GADRequest *r = [[GADRequest alloc] init];
r.testing = YES;
// [bannerView_ loadRequest:r];
[r release];
I figure I'm changing the retain count on bannerView with the loadRequest but I don't know how to fix it. I tried a [bannerView_ release] immediately after the [r release]; line (i.e. releasing locally) but that didn't work. I didn't expect it to because bannerView_ is declared elsewhere. I tried [bannerView_ release]; in the dealloc method but that didn't work. I also tried [bannerView_ autorelease]; locally. The wise heads at Google put [bannerView_ release]; in the ViewDidUnload method.
It's possible that Instruments is just messing with my head. The leak appears after about 10 seconds but the app performs well and the amount of memory leaked doesn't seem to be spirally upwards as the app continues to run. Is there such a thing as a benign memory leak?
Thanks again for your help,
Dessie.