Help with Challenge: Annotation Extras


#1

After five hours, I’ve decided to ask for help. I’m a newbie, and I’m sure there’s a gap in my knowledge that’s not letting me get through this. Please have a peek. Any help appreciated.

From MapPoint.h

[code]#import <Foundation/Foundation.h>
#import <CoreLocation/CoreLocation.h>
#import <MapKit/MapKit.h>

@interface MapPoint : NSObject
{

NSString *title;
NSString *timestamp;
CLLocationCoordinate2D coordinate;

}

@property (nonatomic, readonly) CLLocationCoordinate2D coordinate;
@property (nonatomic, copy) NSString *timestamp;
@property (nonatomic, copy) NSString *title;

  • (id)initWithCoordinate: (CLLocationCoordinate2D)c title:(NSString *)t timestamp:(NSString *)n;

@end
[/code]

MapPoint.m

[code]#import “MapPoint.h”

@implementation MapPoint
@synthesize coordinate, title, timestamp;

  • (id)initWithCoordinate:(CLLocationCoordinate2D)c title:(NSString *)t timestamp:(NSString *)n;
    {
    [super init];
    coordinate = c;
    [self setTitle:t];
    [self setTimestamp:n];
    return self;
    }

  • (void)dealloc
    {
    [title release];
    [timestamp release];
    [super dealloc];
    }

@end
[/code]

Related code from the app delegate:

[code]- (void)locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation
{
NSLog(@“Current location is:%@”, newLocation);
//How many seconds ago was this new location created?
NSTimeInterval t = [[newLocation timestamp] timeIntervalSinceNow];
//CLLocationManagers will return the last found location of
//the device first, you dont’s want that data in this case.
//If this location was made more than 3 minutes ago, ignore it.
if (t < -180) {
//This is cached data, you do’t want it, keep looking
return;
}

NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
 [dateFormatter setTimeStyle:NSDateFormatterNoStyle];
 [dateFormatter setDateStyle:NSDateFormatterMediumStyle];
 
 NSDate *date = [NSDate date];
 



MapPoint *mp = [[MapPoint alloc]
				initWithCoordinate:[newLocation coordinate] 
				title:[locationTitleField text]
				timestamp: [dateFormatter stringFromDate:date];
				[mapView addAnnotation:mp];
[mp release];

[self foundLocation];

[/code]

Thanks for having a look. Appreciate it.


#2

Hi,

I could be wrong as I’m only 1 chapter ahead of you !

I think your problem is that MKAnnotation only has a title and a subtitle property but you’ve added timestamp to your subclass.

MapKit wouldn’t know about timestamp so won’t display it - you could either append the timestamp to the title or use the subtitle.

FWIW.

Gareth


#3

Correct, you will need to configure your own title or subtitle to be a concatenation of some text and the timestamp. MKMapView will only asks annotations for those bits of information and don’t know anything else about the class you have created.


#4

Thanks a lot for the pointers you two. Going to work on it this afternoon. Appreciate it.


#5

Thanks so much for the nudge in the right direction. I was pretty stumped. This code now works exactly as expected…wondrering though, am I leaking memory here? I believe I don’t need to release *theDateAndTime as I did not alloc/init it, but am I missing anything?

Thanks.

[code]- (void)locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation
{
NSLog(@“Current location is:%@”, newLocation);
//How many seconds ago was this new location created?
NSTimeInterval t = [[newLocation timestamp] timeIntervalSinceNow];
//CLLocationManagers will return the last found location of
//the device first, you dont’s want that data in this case.
//If this location was made more than 3 minutes ago, ignore it.
if (t < -180) {
//This is cached data, you do’t want it, keep looking
return;
}

NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setDateFormat:@"MM-dd-yy, "];

NSDateFormatter *timeFormat = [[NSDateFormatter alloc] init];
[timeFormat setDateFormat:@"hh:mm-a"];

NSDate *now = [[NSDate alloc] init];

NSString *theDate = [dateFormat stringFromDate:now];
NSString *theTime = [timeFormat stringFromDate:now];
NSString *theDateAndTime;
theDateAndTime = [theDate stringByAppendingString:theTime];
					  
				  

NSLog(@"\n"
	  
      "theDate: |%@| \n"
      "theTime: |%@| \n"
	  "theDateAndTime: |%@| \n"
      , theDate, theTime, theDateAndTime);

[dateFormat release];
[timeFormat release];
[now release];

[/code]

Thanks for looking, everyone.


#6

Nope, that looks fine to me. You can run the “Build and Analyze” feature in the Build menu, it is pretty good at picking up simple memory leaks.


#7

Interesting…

Here’s my code:

-(id)initWithCoordinate:(CLLocationCoordinate2D)c title:(NSString *)t
{
     [super init];
     coordinate = c;

     NSDate *currentDate = [NSDate date];                                         // Notice that I don't do alloc init of this instance
     NSDateformatter *formatDate = [[[NSDateFormatter alloc] init] autorelease];  // I find that putting "autorelease" in the alloc init lines resolves 
                                                                                  // memory leaks in the analyser
     [formatDate setDateStyle:NSDateFormatterMediumStyle];
     [NSString *dateStamp = [formatDate stringFromDate:currentDate];
     t = [t stringByAppendingString:@", Tagged on: "];
     t = [t stringByAppendingString:dateStamp];
     [self setTitle:t];
     return self;
}

I’m wondering about the two lines I’ve commented in the above code. First, I’m wondering about the fact that if I do put in:

NSDate *currentDate = [[[NSDate date] alloc] init] autorelease];
currentDate = [NSDate date];

I get an analyser warning that the initial value of currentDate is never read.

So what I’m wondering about is:

  1. Is it correct or incorrect to NOT allocate like I did in the code that runs with no problems above?
  2. What is the appropriate form regarding autorelease? I’ve noticed that sometimes people choose to explicitly release instances, whereas autorelease gives a similar effect.

#8

Your first chunk of code looks fine:

  1. You create a NSDate object and have a pointer to it named currentDate. This object is autoreleased, and therefore this method isn’t responsible for it anymore.
  2. You create an instance of NSDateFormatter and have a pointer to it named formatDate. This object is autoreleased, and therefore this method isn’t responsible for it anymore.
  3. Minus the typo, you create an instance of NSString and have a pointer to it named dateStamp. This object is autoreleased and therefore this method isn’t responsible for it anymore.
  4. You create two more NSString instances with stringByAppendingString:, both of which are autoreleased.

So you’ve created 5 objects, all are autoreleased, and for good reason: the pointers to these objects only exist for the duration of this method. Once this method exits, these pointers are gone forever. The only object you want to keep around here is the final title, which you do, by invoking the setter method which will (most likely) copy the string pointed to by t and store it in the instance variable title.

In the second snippet of code, you have one pointer and two objects. currentDate = a brand new date, and then before using that date, you assign currentDate to equal another brand new date object. The two lines or code you have here are actually identical - they both return an NSDate instance that contains the current date and have been autoreleased. The second is simply faster to type - it is a convenience method.

The appropriate use of autorelease is use it when you need to. If you are returning a pointer to an object from a method, you should autorelease it and return it. If you are using an object and it only needs to exist for the scope of this method, release it before the method ends. You could just of easily created the NSDateFormatter object, not autorelease it, and then release it before the method ends. In practice, it doesn’t really matter, it’s only one object. If you were creating thousands of objects within the scope of a single method, you will want to release these objects as soon as you are done with them - no point in stacking up thousands of objects in memory to be destroyed some time later if you can destroy them right now.


#9

Thanks, Joe. What I didn’t understand from the apple dev documentation was that when it says “this method used the default init method” it was referencing that the method implements “self init” thereby allocating and initializing itself from within. After you pointed out it was a convenience method and I went back and reviewed RandomPossession, it all fell into place.

Incidentally I’m working through the book using an iPad rather than an iPhone, and I’m happy to say that I have yet to run into any major difficulties. I doubt I’m going to, as from what i’ve seen, the concepts are identical.


#10

This is my code…

- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation { NSLog(@"%@", newLocation); // How many seconds ago was this new location created? NSTimeInterval t = [[newLocation timestamp] timeIntervalSinceNow]; // CLLocationManagers will return the last found location of the // device first, you don't want that data in this case. // If this location was made more than 3 minutes ago, ignore it if(t < -180) // This is cached data, you don't want it, keep looking return; NSDate *now = [[[NSDate alloc] init] autorelease]; // Create an NSDateFormatter to convert NSDate to string NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; // Set up format for how date and time will be displayed [formatter setDateStyle:kCFDateFormatterLongStyle]; [formatter setTimeStyle:kCFDateFormatterLongStyle]; // Convert to a string. NSString *dateTimeSubtitle = [formatter stringFromDate:now]; MapPoint *mp = [[MapPoint alloc] initWithCoordinate:[newLocation coordinate] title:[locationTitleField text] subtitle:dateTimeSubtitle]; [mapview addAnnotation:mp]; [formatter release]; [mp release]; [self foundLocation]; }