I am using the location manager to generate location data that I send in a URL to down load data. The first time I call the location manager, it correctly returns the current location and based on the current location I can fetch the data from the URL.
However, when I attempt to retrieve the current location a second time, I receive a EXC_BAD_EXCESS.
When I try debugging with NSZombieEnabled it shows me FirstViewController.recievedData as a zombie in the didReceiveResponse method. (see marked code below)
I dug further and I found that after releasing the initial connection an unknown connection is established and then it tries to access receivedData which is already released .
The header file info :`
#import <CoreLocation/CoreLocation.h>
define SECS_OLD_MAX 1
#interface FirstViewController : UIViewController<CLLocationManagerDelegate> {
UIActivityIndicatorView *spinner;
CLLocationManager *locationManager;
CLLocation *startingPoint;
UIButton *relocateMe;
NSMutableData *receivedData;
NSString *lat;
NSString *lon;
}
#property (nonatomic, retain) IBOutlet UIActivityIndicatorView *spinner;
#property (nonatomic, retain) CLLocationManager *locationManager;
#property (nonatomic, retain) CLLocation *startingPoint;
#property (nonatomic, retain) IBOutlet UIButton *relocateMe;
#property (nonatomic, retain) NSMutableData *receivedData;
#property (nonatomic, retain) NSString *lat;
#property (nonatomic, retain) NSString *lon;
`
The .m file code :
//starting the manger :
[spinner startAnimating];
**EDIT**************************ADDED IN THE AUTORELEASE POOL BY HIB********************************
self.locationManager = [[[CLLocationManager alloc] init]autorelease];
// Detecting the user device
NSString *currentDevice = [[UIDevice currentDevice] model];
// if its iPhone then locate the current lattitude and longitude
if([currentDevice isEqualToString:#"iPhone"] || [currentDevice isEqualToString:#"iPhone 3G"] || [currentDevice isEqualToString:#"iPhone 3G S"]){
DLog(#"I have identified the device as an iPhone");
if(locationManager.locationServicesEnabled == YES){
DLog(#"ok now the location manager gets the property");
locationManager.delegate = self;
// This is the most important property to set for the manager. It ultimately determines how the manager will
// attempt to acquire location and thus, the amount of power that will be consumed.
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
// Once configured, the location manager must be "started".
[locationManager startUpdatingLocation] ;
}else {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Oops!"
message:#"Please enable location servies"
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
[alert release];
}
}
//if its iPod then fetch the city based restaurants
else if([currentDevice isEqualToString:#"iPod touch"] || [currentDevice isEqualToString:#"iPod touch 2G"]){
}
else if([currentDevice isEqualToString:#"iPhone Simulator"]){
//TechZen says: there appears to be some code missing here, not sure if its relevant
}
//didupdatetolocation method
- (void)locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation {
// store the location as the "best effort"
DLog(#"Lat = %g Long = %g",newLocation.coordinate.latitude,newLocation.coordinate.longitude);
NSDate *eventDate = newLocation.timestamp;
NSTimeInterval howRecent = [eventDate timeIntervalSinceNow];
DLog(#"NSTIME INTERVAL = %i",howRecent);
//Is the event recent and accurate enough ?
if (abs(howRecent) < SECS_OLD_MAX) {
self.lat = [NSString stringWithFormat:#"%g",newLocation.coordinate.latitude];
self.lon = [NSString stringWithFormat:#"%g",newLocation.coordinate.longitude];
[[NSUserDefaults standardUserDefaults] setObject:lat forKey:#"LATITUDE"];
[[NSUserDefaults standardUserDefaults] setObject:lon forKey:#"LONGITUDE"];
DLog(#"inside Lat = %g Long = %g",newLocation.coordinate.latitude,newLocation.coordinate.longitude);
self.startingPoint = newLocation;
[locationManager stopUpdatingLocation];
**EDIT********************************REMOVED BY HIB******************************
self.locationManager = nil;
[locationManager release];
**EDIT********************************REMOVED BY HIB******************************
**ADDED BY HIB********************************************
locationManager.delegate = nil;
**ADDED BY HIB********************************************
#try {
//passing the parameter for more condition
self.lat = [NSString stringWithFormat:#"%g",startingPoint.coordinate.latitude];
self.lon = [NSString stringWithFormat:#"%g", startingPoint.coordinate.longitude];
NSString *string2 = [[NSString alloc] initWithFormat:#"%#/Service.asmx/someMethod?lat1=%g&lon1=%g&recordSize=0"
,[[NSUserDefaults standardUserDefaults] stringForKey:#"textEntry_key"],startingPoint.coordinate.latitude,startingPoint.coordinate.longitude];
NSURL *url = [[NSURL alloc] initWithString:string2];
[string2 release];
NSMutableURLRequest* request2=[NSMutableURLRequest requestWithURL:url];
[request2 setHTTPMethod:#"GET"];
[request2 setTimeoutInterval:25.0];
[[NSURLCache sharedURLCache] setMemoryCapacity:0];
[[NSURLCache sharedURLCache] setDiskCapacity:0];
NSURLConnection *theConnection=[[NSURLConnection alloc] initWithRequest:request2 delegate:self];
if (theConnection) {
receivedData = [[NSMutableData data]retain];
} else {
// inform the user that the download could not be made
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Sorry !"
message:#"The server is not avaialable \n Please try againa later"
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
[spinner stopAnimating];
}
[url release];
}
#catch (NSException * e) {
}
#finally {
}
}
}
//and the delegate methods
#pragma mark -
#pragma mark connection methods
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
// this method is called when the server has determined that it
// has enough information to create the NSURLResponse
// it can be called multiple times, for example in the case of a
// redirect, so each time we reset the data.
// receivedData is declared as a method instance elsewhere
**************************************the zombie is here *********************************
[receivedData setLength:0];
*****************************************************************************************
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
// append the new data to the receivedData
// receivedData is declared as a method instance elsewhere
[receivedData appendData:data];
}
- (void)connection:(NSURLConnection *)connection
didFailWithError:(NSError *)error
{
[spinner stopAnimating];
// release the connection, and the data object
[connection release];
// receivedData is declared as a method instance elsewhere
[receivedData release];
// inform the user
DLog(#"Connection failed! Error - %# %#",
[error localizedDescription],
[[error userInfo] objectForKey:NSErrorFailingURLStringKey]);
// alert the user in the inter face.
UIAlertView* alert = [[UIAlertView alloc]initWithTitle:#"Sorry !"
message:#"The server is not available.\n Please try again later."
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
[alert release];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
// do something with the data
// receivedData is declared as a method instance elsewhere
DLog(#"Succeeded! Received %d bytes of data",[receivedData length]);
[spinner stopAnimating];
// release the connection, and the data object
if(receivedData == nil)
{
UIAlertView* alert = [[UIAlertView alloc]initWithTitle:#"Sorry !"
message:#"The server is not available.\n Please try again later or select city."
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
[alert release];
[spinner stopAnimating];
}
else
{
//just parse and use the data
}
[connection release];
[receivedData release];
}
Please help . I am stuck.
You have a systematic problem with not properly accessing your class properties. The properties will not be automatically retained and released unless you use the self.propertyName to force the call to the accessors. For example:
[locationManager stopUpdatingLocation]; <-- direct access
self.locationManager = nil; <-- access through generated accessor
[locationManager release]; <-- direct access again with release bypassing the automatic memory management
You should have:
[self.locationManager stopUpdatingLocation];
self.locationManager = nil;
//[locationManager release]; this line is now unneeded because the accessor handles it
You have the same problem with recievedData and startingPoint. In the vast majority of cases, if you use the synthesized accessors you only need to call release on retained properties in your dealloc. Use the accessors will clear up your zombie problem.
Without knowing where the EXC_BAD_ACCESS occurs I can't say definitively but since that error often occurs when messaging an non-existant object I can say that it is very likely that your bypassing of the property accessors and your releasing of them manually is probably causing the code to send to nilled property.
Fix the access and see if that resolves the problem.
Edit01:
TechZen the problem is removed 50 %.
my application runs nicely in the
debugging mode but when I pull out the
cable and starts again it crashes .
the problem is certainly with location
manager . but I am not clear about
retains and release of location
manager . can you help me
I'll take a stab at it. For your memory management:
Always access your
self.locationManager using the
self-dot-propertyName notation to
make sure you utilize the
retention/release mechanism of
generated accessors.
Never call release on any property
except in the dealloc method. If
you use the self-dot notation and
have the property set to retain, all
but the end of life release is
handled automatically for you. This
includes times when you nil the
property or set it to another object.
When in doubt, don't release. It's easier to fix a memory leak latter than it is to track down a bug caused by an object that disappears at random points in the code because its retain count is skewed. Trying to hard to prevent leaks when your learning the environment is a form of premature optimization that causes more trouble than it prevents.
I note that in your locationManager:didUpdateToLocation:fromLocation: method you don't actually query the locationManager passed to the method but instead query the class's self.locationManager property. This may or not be a problem but it is best to use the passed in manager to make sure you are in fact querying the manager instance that updated. I also don't think it necessary to destroy and recreate the location manager repeatedly. I think you can initialize it once and keep it around (check the docs on that.)
If cleaning up your property references and using the passed manager does not help, I suggest you post a new question with the cleaned up code. At that point you will legitimately have a new issue and besides we need to see the cleaned up code to spot the problem.
Edit02:
(Based on new code)
You don't need to autorelease your 'self.locationManager' property here:
self.locationManager = [[[CLLocationManager alloc] init]autorelease];
You only use autorelease when you create an object and in your class and then send it to another class. You never autorelease properties of class.
You need to stop trying to release your declared properties. You never release properties defined with retain except in the dealloc method. You are stepping on the properties generated accessors that maintain the retain count automatically.
Your are still not using the accessors consistently. This:
if(locationManager.locationServicesEnabled == YES){
DLog(#"ok now the location manager gets the property");
locationManager.delegate = self;
// This is the most important property to set for the manager. It ultimately determines how the manager will
// attempt to acquire location and thus, the amount of power that will be consumed.
locationManager.desiredAccuracy = kCLLocationAccuracyBest;
// Once configured, the location manager must be "started".
[locationManager startUpdatingLocation] ;
should be:
if(self.locationManager.locationServicesEnabled == YES){
DLog(#"ok now the location manager gets the property");
self.locationManager.delegate = self;
// This is the most important property to set for the manager. It ultimately determines how the manager will
// attempt to acquire location and thus, the amount of power that will be consumed.
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest;
// Once configured, the location manager must be "started".
[self.locationManager startUpdatingLocation] ;
and this:
locationManager.delegate = nil;
should be:
self.locationManager.delegate = nil; //<-- why are you doing this anyway?
You need to track down all references to all your declared properties and affix self. to each one (expect inside a properties custom accessor which you don't seem to have use -- which is good in this case.)
I strongly suspect your problem is your unnecessary fiddling with the retention of the self.locationManager property. You maybe causing the location manager to disappear at random.
You are still not using the passed manager in locationManager:didUpdateToLocation:fromLocation: I suggest you do so or at least test that the passed manager is the same object as your self.locationManager. simply replace self.locationManager with manager.
One think you are certainly doing wrong: you need to allocate receivedData before you start the NSURLConnection. It will fork in the background right when you alloc/init it, so receivedData needs to be ready before, not after.
You are releasing recievedData at the end of a connection but are not setting your pointer to nil - it will still be pointing at where recievedData used to be.
Instead of
[recievedData release];
try
self.recievedData = nil;
Hope that helps,
Sam
I could not find the source of your problem but you have a leak in
self.locationManager = [[CLLocationManager alloc] init];
you should use
self.locationManager = [[[CLLocationManager alloc] init] autorelease];
instead.
Edit: Download Charles Web Proxy, check what connections you are making, what responses you get, and maybe we'll have a better idea then.
Edit after Comments: The autogenerated accessor property defined to retain automatically retains the passed object, and releases it when you set the property to nil/or release. So it soes ITS job, but its YOUR job to keep track of memory management of the passed object. So, yes, the initial code above has a LEAK, and you should do your job and RELEASE/AUTORELEASE your ALLOCATED object, which in this case happens to be [[CLLocationManager alloc] init].
Edit :
I don't know how this comment can get -1. It's simple memory management. The answers on this thread all agree this is a correct post:
iPhone: Is this a leak or not
I am not sure what was the actual problem . but when I was comparing the apple LocateMe example I see locatiomManager.delegate = nil; It solves the problem completely .
Related
I have been searching for this problem on the SOF for several days and I still have not found the solution (say the same problem) yet.
I'm making and app that downloads 5 images simultaneously in an URL list (each image is on a different server).
I have an ImageDownloader class subclasses NSOperation and implements the NSURLConnectionDataDelegate.
So that I can add an instance of ImageDownloader to an operationQueue in the ViewController and it will run in a separate thread under the operationQueue. The line that add the downloader to the operationQueue is here:
downloader = [[ImageDownloader alloc] init];
[downloader downloadImageWithURL:[controller.URList objectForKey:[NSString stringWithFormat:#"%d",downloadIndex]] queue:queue andTag:downloadIndex + 100]; //my custom initialize
downloader.delegate = self;
[queue addOperation:downloader]; //use the addOperation method
Everything works fine in iOS6 but messed up in iOS5 (5.0 on my test device and 5.1 on my SDK), it just doesn't receive any response nor data by performing the methods didReceiveResponse and didReceiveData at all (these 2 methods are not jumped in).
After the timeout was exceeded, the runloop jumps into didFailWithError method and the program stalls.
As I understand, this means the runloop still runs right?
I tried to print out the error and all I got is: The request timed out.
When I reduce the number of downloading instances to 2 then it runs, but not with >=3 downloading instances.
One more information is that my network connection does limit the number of connection. But it work fine in iOS6, why it just doesn't work on iOS5?
I can still load the web in the simulator while the app is downloading.
So what kind of problem is this and how can I get over this problem?
Thanks in advance.
*Update:* as there are many classes and the problem's not been clearly detected yet, I will share here the whole project. You can download it directly from here:
DownloadingImage
As I just found out, if you're using credentials there is a chance that the server will reject them randomly every once in a while. So if you have a check to make sure previousFailureCount == 0 then you will most likely have a bug.
I've just figured out where my problem is, but not really understand why.
In my ImageDownloader class, I set up a runloop with done and currentRunLoop variables.
In the main method, I have a while loop for forcing the currentRunLoop run.
As I remove those "runLoop" stuffs, the app runs smoothly on both iOS6 and iOS5.
So change the entire ImageDownloader.m with these lines then it works (I commented out some useless (say harmful) lines):
//
// ImageLoader.m
// DownloadImagesTableView
//
// Created by Viet Ta Quoc on 6/25/13.
// Copyright (c) 2013 Viet Ta Quoc. All rights reserved.
//
#import "ImageDownloader.h"
#implementation ImageDownloader
#synthesize downloadData,delegate,queue,done,customTag;
NSRunLoop *currentRunLoop;
-(void)downloadImageWithURL:(NSString *)imageUrl queue:(NSOperationQueue*)opQueue andTag:(int)tag{
self.customTag= tag;
self.queue = opQueue;
// self.done = NO;
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:imageUrl] cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:30];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
[connection start];
// currentRunLoop = [NSRunLoop currentRunLoop];
NSLog(#"Start downloading image %d...",customTag);
}
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
NSLog(#"Received response...");
downloadData=[[NSMutableData alloc] initWithLength:0];
expectedDataLength=[response expectedContentLength];
NSLog(#"Image %d size: %lld kb",customTag,[response expectedContentLength]/1024);
}
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
float receivedLenght = [data length];
receivedDataLength=(receivedDataLength+receivedLenght);
float progress=(float)receivedDataLength/(float)expectedDataLength;
[delegate updateProgess:progress andIndex:[NSIndexPath indexPathForRow:customTag-100 inSection:0]];
[self.downloadData appendData:data];
// NSLog(#"Percentage of data received of tag %d: %f %%",self.customTag,progress*100);
}
-(void)connectionDidFinishLoading:(NSURLConnection *)connection{
[delegate finishedDownloadingImage:downloadData andTag:customTag];
// done = YES;
}
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
UIAlertView *alert=[[UIAlertView alloc]initWithTitle:#"Warning" message:#"Network Connection Failed?" delegate:self cancelButtonTitle:#"Cancel" otherButtonTitles:nil, nil];
// NSLog(#"%#",[error debugDescription]);
NSLog(#"Connection failed! Error - %# %#",[error localizedDescription],[[error userInfo] objectForKey:NSURLErrorFailingURLStringErrorKey]);
[alert show];
}
-(void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge{
NSLog(#"Got here *(*&(**&(*&(*&(*&(*&(*&(*&(*&(*&(*&(*&(*&(*&(*&(*&(*&(*&");
}
-(void)main{
// do{
//// NSLog(#"Running....1");
// [currentRunLoop runUntilDate:[NSDate distantFuture]];
// // [currentRunLoop run];
// } while (!done);
// [currentRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.01]];
}
#end
Thank you guys for your supports.
==================================================================================
P/s: for anyone who interested in this problem, I update here my entire solution: DownloadImage_Final
I get leaks if I dont put it in dealloc. I get a crash EXC_BAD_ACCESS If I do. I cannot see anything wrong with this code. The bad access is pointed at [events release]. Have I made a mistake in the code below or is Instruments just having a laugh at my expense?
events is an NSArray
#interface EventsViewController : UITableViewController
{
#private
NSArray *events;
}
- (void)viewDidLoad
{
events = [[self getEvents] retain];
}
- (void)dealloc
{
[events release];
[super dealloc];
}
- (NSArray*)getEvents
{
NSMutableArray *response = [[[NSMutableArray alloc] init] autorelease];
//Some sql
while(sqlite3_step(statement) == SQLITE_ROW)
{
Event *event = [[[Event alloc] init] autorelease];
event.subject = [NSString stringWithUTF8String:(char *)sqlite3_column_text(statement, 0)];
[response addObject:event];
}
return response;
}
Update
A lot of you are saying the code is fine which is a plus. I dont manipulate events elsewhere - I have removed any code that does to try and single out the crash. Perhaps its in the parent view?
This is the click event that pushes the EventsViewController:
- (void)eventsClick:(id)sender
{
EventsViewController *eventsViewController = [[EventsViewController alloc] initWithNibName:#"EventsViewController" bundle:nil];
eventsViewController.anywhereConnection = anywhereConnection;
eventsViewController.contact = contact;
[[self navigationController] pushViewController:eventsViewController animated:YES];
[eventsViewController release];
}
The crash is actually happening when I return to the parent view. (I think it is considered a parent in this scenario). But perhaps the [eventsViewController release] just triggers dealloc in the EventViewController.
Have you considered just refactoring your code to use ARC? It works with iOS 4 and up and will make your life a lot easier. There are plenty of tutorials out there that will guide you how to do it, and will remove the need to manually figure out the nuances of memory management.
If your Events object has property 'subject' set as assign, then the results of stringWithUTF8String: will not be retained. (Same thing if Events is a C++ object.)
The stringWithUTF8String: method returns an auto-released object that will be released at the next turn of the event loop.
There is a huge difference when you reference a variable via "self", and when you don't.
When you use
events = [[self getEvents] retain];
the memory allocated in getEvents never gets stored in the class property and is basically a leak.
You need to use
self.events = [self getEvents]; // no need to use retain if property is correctly defined.
Then
[events release];
should work fine.
try putting
events = nil;
in dealloc.
I can't figure out how come the row count is correct but the uitableview won't load the row contents the NSLog shows carresults=(null), but the row count is correct, on the simulator if I relaunch, the carresults get filled. It seems like I'm missing my first fetchedResultsController teh first time through, but how can it get the row count if it doesn't know what' there?
Help!! any Ideas? Thanks, Mike
The titleForHeaderInSection works fine, brings back the correct titles:
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
return [[[fetchedResultsController1 sections] objectAtIndex:section] name];
}
This brings back the correct row count:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
id <NSFetchedResultsSectionInfo> sectionInfo = [[fetchedResultsController1 sections] objectAtIndex:section];
return [sectionInfo numberOfObjects];
}
This does not populate the cells until a rebuild on simulator, never populates the iPhone.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *FirstViewIdentifier = #"FirstViewIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:FirstViewIdentifier];
if (cell == nil) {
[[NSBundle mainBundle] loadNibNamed:#"Cell" owner:self options:nil];
cell = firstviewCell;
self.firstviewCell = nil;
}
Cars *carresults = (Cars *)[fetchedResultsController1 objectAtIndexPath:indexPath];
NSLog(#"carresults %#", carresults.make);
EDIT: Here is the FRC:
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:[[[UIApplication sharedApplication] delegate] managedObjectContext] sectionNameKeyPath:#"key" cacheName:#"Root1"];
self.fetchedResultsController = aFetchedResultsController;
fetchedResultsController.delegate = self;
Since you haven't provided any debug info I can only guess as to what's wrong, so I'll ask you some questions. Does it in fact instantiate the firstviewCell property with the nib. If the cell is not connected to the firstviewCell property of the File Owner (in the nib) it won't work. Otherwise if the fetchedResultsController doesn't have anything in it you'll get an error if you try to access the data. If the nslog fires then you probably didn't get an error, which means that your Cars objects are being fetched theres just nothing in them. to see whats in the fetchedResultsController call NSLog(#"Fetched Objects: %#",[[fetchedResultsController fetchedObjects] description]); Keep in mind though, that fetchedObjects is only updated when you call performFetch. Since you say carresults gets filled when you restart the app it may be possible that you need to call saveContext for the results to get loaded. The only reason for this is if you create the data at runtime, before the table view gets loaded. Otherwise I would assume you have the table view set as the fetched results controller's delegate so that it gets informed of any changes and responds appropriately. The app delegate usually does this on applicationWillResign active or applicationWillTerminate (applicationWillTerminate doesn't seem to get called by iOS4 during normal closing).The only other thing I could think of is that maybe your SectionInfo object might contain the wrong information, try debugging that too.
Good Luck,
Rich
Edit: I appologize, the save context method is a method added to your appdelegate when you create an app based on core data. a good way to make a core data stack is to wrap it in an NSObject, it can be useful to make it a singleton ,unless of course you need concurrency in which case it gets really complicated. This is the implementation including the save context function:
// CoreDataStack.h
// do not call alloc, retain, release, copy or especially copyWithZone: (because I didn't bother to override it since you shouldn't try to create this in anything but the main thread, and definatly don't dispatchasync this object's methods)
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#define kYourAppName #"This should be replaced by the name of your datamodel"
#interface CoreDataStack : NSObject {
#private
NSManagedObjectContext *managedObjectContext_;
NSManagedObjectModel *managedObjectModel_;
NSPersistentStoreCoordinator *persistentStoreCoordinator_;
}
#property (nonatomic, retain, readonly) NSManagedObjectContext *managedObjectContext;
#property (nonatomic, retain, readonly) NSManagedObjectModel *managedObjectModel;
#property (nonatomic, retain, readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator;
+ (CoreDataStack *)sharedManager;
+ (void)sharedManagerDestroy;
// call this in your app delegate in applicationWillTerminate and applicationWillResignActive
- (void)saveContext;
- (NSURL *)applicationLibraryDirectory;
#end
// CoreDataStack.m
#import "CoreDataStack.h"
#interface CoreDataStack ()
- (oneway void)priv_release;
#end
#implementation CoreDataStack
static CoreDataStack *sharedManager = nil;
+ (CoreDataStack *)sharedManager {
if (sharedManager != nil) {
return sharedManager;
}
sharedManager = [[CoreDataStack alloc] init];
return sharedManager;
}
+ (void)sharedManagerDestroy {
if (sharedManager) {
[sharedManager priv_release];
sharedManager = nil;
}
}
- (id)retain {
return self;
}
- (id)copy {return self;}
- (oneway void)release{}
- (oneway void)priv_release {
[super release];
}
- (void)saveContext {
NSError *error = nil;
if (managedObjectContext_ != nil) {
if ([managedObjectContext_ hasChanges] && ![managedObjectContext_ save:&error]) {
/*
Replace this implementation with code to handle the error appropriately.
abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. If it is not possible to recover from the error, display an alert panel that instructs the user to quit the application by pressing the Home button.
*/
//abort();
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Error"
message:#"The app has run into an error trying to save, please exit the App and contact the developers. Exit the program by double-clicking the home button, then tap and hold the iMean icon in the task manager until the icons wiggle, then tap iMean again to terminate it"
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
[alert release];
}
}
}
#pragma mark -
#pragma mark Core Data stack
/**
Returns the managed object context for the application.
If the context doesn't already exist, it is created and bound to the persistent store coordinator for the application.
*/
- (NSManagedObjectContext *)managedObjectContext {
if (managedObjectContext_ != nil) {
return managedObjectContext_;
}
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
if (coordinator != nil) {
managedObjectContext_ = [[NSManagedObjectContext alloc] init];
[managedObjectContext_ setPersistentStoreCoordinator:coordinator];
}
return managedObjectContext_;
}
/**
Returns the managed object model for the application.
If the model doesn't already exist, it is created from the application's model.
*/
- (NSManagedObjectModel *)managedObjectModel {
if (managedObjectModel_ != nil) {
return managedObjectModel_;
}
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:#"kYourAppName" withExtension:#"momd"];
managedObjectModel_ = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
return managedObjectModel_;
}
/**
Returns the persistent store coordinator for the application.
If the coordinator doesn't already exist, it is created and the application's store added to it.
*/
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (persistentStoreCoordinator_ != nil) {
return persistentStoreCoordinator_;
}
NSString *yourAppName = [[NSString stringWithFormat:#"%#.sqlite",kYourAppName] autorelease];
NSURL *storeURL = [[self applicationLibraryDirectory] URLByAppendingPathComponent:yourAppName];
NSError *error = nil;
persistentStoreCoordinator_ = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![persistentStoreCoordinator_ addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
/*
Replace this implementation with code to handle the error appropriately.
abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. If it is not possible to recover from the error, display an alert panel that instructs the user to quit the application by pressing the Home button.
Typical reasons for an error here include:
* The persistent store is not accessible;
* The schema for the persistent store is incompatible with current managed object model.
Check the error message to determine what the actual problem was.
If the persistent store is not accessible, there is typically something wrong with the file path. Often, a file URL is pointing into the application's resources directory instead of a writeable directory.
If you encounter schema incompatibility errors during development, you can reduce their frequency by:
* Simply deleting the existing store:
[[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil]
* Performing automatic lightweight migration by passing the following dictionary as the options parameter:
[NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES],NSMigratePersistentStoresAutomaticallyOption, [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
Lightweight migration will only work for a limited set of schema changes; consult "Core Data Model Versioning and Data Migration Programming Guide" for details.
*/
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
// abort();
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Error"
message:#"The app has run into an error trying to load it's data model, please exit the App and contact the developers. Exit the program by double-clicking the home button, then tap and hold the iMean icon in the task manager until the icons wiggle, then tap iMean again to terminate it"
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
[alert release];
}
return persistentStoreCoordinator_;
}
#pragma mark -
#pragma mark Application's Library directory
/**
Returns the URL to the application's Documents directory.
*/
// returns the url of the application's Library directory.
- (NSURL *)applicationLibraryDirectory {
return [[[NSFileManager defaultManager] URLsForDirectory:NSLibraryDirectory inDomains:NSUserDomainMask] lastObject];
}
#pragma mark -
#pragma mark Memory management
- (void)dealloc {
// release and set all pointers to nil to avoid static issues
[managedObjectContext_ release];
managedObjectContext_ = nil;
[managedObjectModel_ release];
managedObjectModel_ = nil;
[persistentStoreCoordinator_ release];
persistentStoreCoordinator_ = nil;
[super dealloc];
}
#end
what i'm trying to reach is to display annotation with the city name.
So I have a class MapPoint :
#interface MapPoint : NSObject<MKAnnotation,MKReverseGeocoderDelegate> {
NSString* title;
NSString* cityName;
CLLocationCoordinate2D coordinate;
MKReverseGeocoder* reverseGeo;
}
#property (nonatomic,readonly) CLLocationCoordinate2D coordinate;
#property (nonatomic,copy) NSString* title;
#property (nonatomic,copy) NSString* cityName;
-(id) initWithCoordinate:(CLLocationCoordinate2D)c tilte:(NSString*)t;
#end
I implemented it like this :
#implementation MapPoint
#synthesize title,coordinate,cityName;
-(id) initWithCoordinate:(CLLocationCoordinate2D)c tilte:(NSString*)t
{
[super init];
coordinate = c;
reverseGeo = [[MKReverseGeocoder alloc] initWithCoordinate:c];
reverseGeo.delegate = self;
[reverseGeo start];
[self setTitle:t];
return self;
}
- (void)reverseGeocoder:(MKReverseGeocoder *)geocoder didFindPlacemark:(MKPlacemark *)placemark
{
NSString* city = [placemark.addressDictionary objectForKey:(NSString*)kABPersonAddressCityKey];
NSString* newString = [NSString stringWithFormat:#"city-> %#",city];
[self setTitle:[title stringByAppendingString:newString]];
}
-(void)reverseGeocoder:(MKReverseGeocoder *)geocoder didFailWithError:(NSError *)error{
NSLog(#"error fetching the placemark");
}
-(void)dealloc
{
[reverseGeo release];
[cityName release];
[title release];
[super dealloc];
}
#end
Then, in my CoreLocation delegate I use MapPoint like that:
-(void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation
{
MapPoint* mp = [[MapPoint alloc] initWithCoordinate:[newLocation coordinate] tilte:[locationTitleField text]];
[mapView addAnnotation:mp];
[mp release];
}
Now, I have 2 issues I'm not sure of :
Is it correct to put reverseGeo as a data member , or a better option would be to just
alloc it inside the initializer and release it inside the didFindPlacemark/didFailWithError delegates(is it even possible to release it there) ?
How can I make sure then when my annotation get displayed I know for sure that the reverseGeo came back with an answer (placemark or error - whatever it is).
Maybe it's just wrong to wait for network response and I should leave it like that - I'm just not sure then when/if network response will arrive it will update the annotationView within the MapView accordingly.
Please elaborate as much as you can.
Thanks
It's fine to store it as a data member.
It looks like you're leaving a trail of annotations for the user's current location? If you're supplementing a regular user's-current-location annotation with a "bread crumb trail" showing where the user has been, then you need to wait to add the point to the map until you get the annotation back (if that's the behavior you want). I would either do that by making the class that manages your map be the MKReverseGeocoder delegate (and have it set the title property and then add the annotation to the map in reverseGeocoder:didFindPlacemark) or add a map reference to your MapPoint class and have it add itself to the map in the same callback.
By the way, the documentation for MKReverseGeocoder includes the following text:
When you want to update the location automatically (such as when the user is moving), reissue the reverse-geocoding request only when the user's location has moved a significant distance and after a reasonable amount of time has passed. For example, in a typical situation, you should not send more than one reverse-geocode request per minute.
I am trying to clean up my code from memory leaks and I am having problems with the 'release' method.
Here is my code:
NSArray *dict = [[NSArray alloc] initWithContentsOfURL:url];
if (dict == nil) {
UIAlertView *alert = [[UIAlertView alloc]
initWithTitle:#"Error"
message:#"Cannot retrieve content. Please try again later."
delegate:self
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
[alert release];
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
return;
}
self.schedule = dict;
[dict release];
[url release]; //I receive a runtime error here, "BAD ACCESS"
I don't understand why when I don't get the same problem with the line above [dict release];
Since you didn't post the code showing how URL was created, here's a general rule to follow:
If create the object with a initializer that starts with "init", then you should probably release it. If it's created another way (convenience method), then it's autoreleased. For example:
NSArray *a = [[NSArray alloc]initWithContentsOfURL:url]; // release this later
NSArray *a = [NSArray arrayWithContentsOfURL:url]; // this will be auto released
Basically you just need to look at whether the framework gave you an autoreleased object or not, because you can't release an autoreleased object or you'll (obviously) get a crash.
Take a look at the Memory Management Guide. It should be required reading.
You are responsible for calling release eat time, you call either alloc, copy, or retain.
In this case you called alloc on dict, but (presumably, although it is not shown where url comes from) not on url.
Objective-C allows you to send messages (e.g. 'release') to nil pointers without consequence.
If the pointer is non-nil and points to something bogus (i.e. an object that's been released), you'll get an EXC_BAD_ACCESS exception. Where does the url parameter come from and what is its retain count ([url retainCount]) before you call release?