Silver Challenge issue


#1

i attempted the challenge using MVCS paradigm.
BNRMapPoint.h

[code]#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
#import <CoreLocation/CoreLocation.h>
@interface BNRMapPoint : NSObject<MKAnnotation, NSCoding>
{
double latitude;
double longitude;
@public NSArray *mapPoints;
}
-(id) initWithCoordinate:(CLLocationCoordinate2D)c title:(NSString *)t subTitle:(NSString *)st;

//this is required property in MKAnnotation Protocol
@property (nonatomic, readonly) CLLocationCoordinate2D coordinate;

//this is an optional property in MKAnnotation Protocol
@property (nonatomic, copy) NSString *title;
//this is an optional property in MKAnnotation Protocol
@property (nonatomic,readonly,copy) NSString *subTitle;
@end
[/code]

BNRMapPoint.m (Model)

[code]#import “BNRMapPoint.h”

@implementation BNRMapPoint
@synthesize coordinate=_coordinate;
@synthesize title=_title;
@synthesize subtitle=_subtitle;

-(void)encodeWithCoder:(NSCoder *)aCoder{
latitude = self.coordinate.latitude;
longitude = self.coordinate.longitude;

[aCoder encodeObject:self.title forKey:@"title"];
[aCoder encodeObject:_subTitle forKey:@"subTitle"];
[aCoder encodeDouble: latitude forKey:@"latitude"];
[aCoder encodeDouble:longitude forKey:@"longitude"];

}
-(id)initWithCoder:(NSCoder *)aDecoder{
if (self= [super init]) {
[self setTitle:[aDecoder decodeObjectForKey:@“title”]];
self->_subTitle= [aDecoder decodeObjectForKey:@“subTitle”];
latitude= [aDecoder decodeDoubleForKey:@“latitude”];
longitude= [aDecoder decodeDoubleForKey:@“longitude”];
}
return self;
}

-(id)initWithCoordinate:(CLLocationCoordinate2D)c title:(NSString *)t subTitle:(NSString *)st{
if (self= [super init]) {
self.coordinate=c;
self.title=t;
_subtitle=st;
}
return self;
}
-(id) init{
return [self initWithCoordinate:CLLocationCoordinate2DMake(43.07, -89.32) title:@“Hometown” subTitle:self.subtitle];
}

@end
[/code]

WhereamiViewController.h

[code]#import <UIKit/UIKit.h>
#import <CoreLocation/CoreLocation.h>
#import <MapKit/MapKit.h>
#import “BNRMapPoint.h”
#import “RootObject.h”
@interface WhereamiViewController : UIViewController<CLLocationManagerDelegate,MKMapViewDelegate,UITextFieldDelegate>
{
@public RootObject *rootObj;
CLLocationManager *locationManager;

IBOutlet MKMapView *worldView;
IBOutlet UIActivityIndicatorView *activityIndicator;
IBOutlet UITextField *locationTitleField;

}
-(IBAction)buttonDidGetPressed:(id)sender;
-(BOOL)textFieldShouldReturn:(UITextField *)textField;
-(void)findLocation;
-(void)foundLocation:(CLLocation *)loc;

@end
[/code]

WhereamiViewController.m (ViewController)

[code]#import “WhereamiViewController.h”

@interface WhereamiViewController ()

@end

@implementation WhereamiViewController

-(IBAction)buttonDidGetPressed:(UISegmentedControl *)sender{//Silver challenge
NSLog(@"%@",NSStringFromSelector(_cmd));
if([sender selectedSegmentIndex]==0){
[worldView setMapType:MKMapTypeStandard];
}
else if([sender selectedSegmentIndex]==1){
[worldView setMapType:MKMapTypeHybrid];
}
else if([sender selectedSegmentIndex]==2){
[worldView setMapType:MKMapTypeSatellite];
}
}
-(id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil{
NSLog(@"%@", NSStringFromSelector(_cmd));
if (self=[super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
rootObj= [[RootObject alloc] init];
locationManager= [[CLLocationManager alloc] init];

    [locationManager setDelegate:self];//self is Whereamicontroller. The delegate pointer is of type id<CLLocationManagerDelegate> and is an ivar of CLLocationManager.
    [locationManager setDesiredAccuracy:kCLLocationAccuracyBest];      
}
NSLog(@"test");
return self;

}

-(void) viewDidLoad{
// [worldView setMapType:MKMapTypeSatellite]; Bronze challenge
[worldView setShowsUserLocation:YES];
}
-(void)findLocation{
NSLog(@"%@",NSStringFromSelector(_cmd));
[locationManager startUpdatingLocation];//This calls locationManager:didUpdateLocations:
NSLog(@“location updated”);
[activityIndicator startAnimating];
[locationTitleField setHidden:YES];
}
-(BOOL)textFieldShouldReturn:(UITextField *)textField{
NSLog(@"%@",NSStringFromSelector(_cmd));
[self findLocation];
[textField resignFirstResponder];
return YES;
}

-(void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation{
NSLog(@"%@",NSStringFromSelector(_cmd));
CLLocationCoordinate2D centerCoordinate= [[userLocation location] coordinate]; //get the coordinate of current location.
MKCoordinateSpan span= MKCoordinateSpanMake(250, 250);//Structure members
MKCoordinateRegion mapPortionToDisplay= MKCoordinateRegionMakeWithDistance(centerCoordinate, span.latitudeDelta, span.longitudeDelta);//span.latitudeDelta=250 and span.longitudeDelta=250
[worldView setRegion:mapPortionToDisplay animated:YES];
// [worldView setRegion:mapPortionToDisplay];
}

-(void)locationManager:(CLLocationManager *)manager didUpdateHeading:(CLHeading *)newHeading{
NSLog(@"%@",NSStringFromSelector(_cmd));
NSLog(@“Heading %@”,newHeading);
}
-(void)foundLocation:(CLLocation *)loc{
NSLog(@"%@",NSStringFromSelector(_cmd));
CLLocationCoordinate2D coord= [loc coordinate];

NSDateFormatter *formatter= [[NSDateFormatter alloc] init];

NSDate *currentDate= [[NSDate alloc] init];
[formatter setDefaultDate:currentDate];

BNRMapPoint *bmp= [[BNRMapPoint alloc] initWithCoordinate:coord title:[locationTitleField text] subTitle:[[formatter defaultDate] description]];
[rootObj.mapPoints addObject:bmp];

[worldView addAnnotation:bmp];

MKCoordinateRegion region= MKCoordinateRegionMakeWithDistance(coord,250,250);
[worldView setRegion:region animated:YES];

//RESET the UI
[locationTitleField setText:@" "];
[activityIndicator stopAnimating];
[locationTitleField setHidden:NO];
[locationManager stopUpdatingLocation];

}
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations{ //CLLocationManagerDelegate method implementation

NSLog(@"%@", NSStringFromSelector(_cmd));
//    NSTimeInterval t0=[[locations lastObject] timeIntervalSinceNow];

NSLog(@"%@",(CLLocation *)[locations lastObject]);
NSTimeInterval t= [[(CLLocation *)[locations lastObject] timestamp] timeIntervalSinceNow];

if (t<-180) {
    return; //No op
}

[self foundLocation:[locations lastObject]];

}

-(void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error{
NSLog(@“Could not find location: %@”,error);//CLLocationManagerDelegate method implementation
}

@end
[/code]

RootObject.h

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

@interface RootObject : NSObject
@property NSMutableArray *mapPoints;
-(BOOL)saveChanges;
-(NSString *)dataObjectArchivePath;
@end[/code]

RootObject.m (Store)

[code]#import “RootObject.h”

@implementation RootObject
@synthesize mapPoints;

-(NSString *)dataObjectArchivePath{
NSArray *documentDirectories= NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentDirectory= [documentDirectories objectAtIndex:0];

NSString *finalPath= [documentDirectory stringByAppendingPathComponent:@"items.archive"];
return finalPath;

}
-(BOOL)saveChanges{
NSString *path= [self dataObjectArchivePath];
return [NSKeyedArchiver archiveRootObject:self.mapPoints toFile:path];writing to the file items.archive
}
-(id)init{
if (self=[super init]) {
NSString *path= [self dataObjectArchivePath];
self.mapPoints= [NSKeyedUnarchiver unarchiveObjectWithFile:path];//reading from the file items.archive

    if (!self.mapPoints) {
        self.mapPoints= [[NSMutableArray alloc] init];
    }
}
return self;

}
@end[/code]

As you analyze from the code, i haven’t made a singleton object of RootObject (store). Instead i instantiated this class in the ViewController’s designated initializer once through an ivar of type RootObject. Am i doing something fundamentally wrong??
The thing is that the data is saving fine. However when it comes to reading, the app crashes with an error. After viewing this a couple of times, i manually deleted the items.archive from the Library folder, and clean- built the code. Now the app wouldn’t even save stuff. :confused: :confused:
Here is the entire console log:

2014-01-17 12:31:32.210 Whereami[523:907] initWithNibName:bundle: 2014-01-17 12:31:32.214 Whereami[523:907] test 2014-01-17 12:31:32.907 Whereami[523:5203] ADDRESPONSE - ADDING TO MEMORY ONLY: http://gsp1.apple.com/pep/gcc 2014-01-17 12:31:36.214 Whereami[523:907] mapView:didUpdateUserLocation: 2014-01-17 12:32:42.963 Whereami[523:907] mapView:didUpdateUserLocation: 2014-01-17 12:33:08.680 Whereami[523:907] textFieldShouldReturn: 2014-01-17 12:33:08.681 Whereami[523:907] findLocation 2014-01-17 12:33:08.682 Whereami[523:907] location updated 2014-01-17 12:33:08.686 Whereami[523:907] locationManager:didUpdateLocations: 2014-01-17 12:33:09.229 Whereami[523:907] <+28.67028400,+77.19638600> +/- 5.00m (speed -1.00 mps / course -1.00) @ 1/17/14, 12:33:08 PM India Standard Time 2014-01-17 12:33:09.229 Whereami[523:907] foundLocation: 2014-01-17 12:33:09.250 Whereami[523:907] -[BNRMapPoint setCoordinate:]: unrecognized selector sent to instance 0x1c139f30 2014-01-17 12:33:09.304 Whereami[523:907] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[BNRMapPoint setCoordinate:]: unrecognized selector sent to instance 0x1c139f30' *** First throw call stack: (0x18aa012 0x126be7e 0x19354bd 0x127f7ea 0x1899cf9 0x189994e 0x49b6 0x38d6 0x3d21 0x11124a 0x11093f 0x10e9c5 0x10a175 0x1869920 0x182cd31 0x1850c51 0x184ff44 0x184fe1b 0x1ce97e3 0x1ce9668 0x1afffc 0x261d 0x31b470d) libc++abi.dylib: terminate called throwing an exception

The problem is in foundLocation method and this is the root cause [quote]-[BNRMapPoint setCoordinate:]: unrecognized selector sent to instance 0x1c139f30[/quote].
How could it be an unrecognized selector?? :confused: :confused: :confused:
Please advise anybody…


#2

[quote]The problem is in foundLocation method and this is the root cause
-[BNRMapPoint setCoordinate:]: unrecognized selector sent to instance 0x1c139f30
.
How could it be an unrecognized selector?? [/quote]
What is the consequence of declaring a readonly property and not implementing a setter?

[quote]#import <Foundation/Foundation.h> ... @interface BNRMapPoint : NSObject<MKAnnotation, NSCoding> ... @property (nonatomic, readonly) CLLocationCoordinate2D coordinate; ... @end[/quote]


#3

Yeah dude…u are right!!! I changed it. I was solving the challenge late in the night while listening to a podcast… So i might not have paid attention or must have overlooked it. Anyways i had the coordinate property remain readonly. I assigned its object pointer _coordinate the external argument. It fixed the problem. I then cleared the Derived Data to remove all the cruft. Then rebuilt the code and then ran it. Works fine now. All the MapPoints are getting saved and then getting read after having relaunched the app from the background state. But, and here’s my question, shouldn’t the previously saved MapPoints annotation(s), be reflected on the MKMapView after having relaunched the app??

And another burning question— After the app is relaunched, shouldn’t the NSMutableArray reference, in the init method, return the initial memory address where the array object from the previous run stored the MapPoints. While running the code second time we reinitialize the object pointer mapPoints thus making it point to a brand new NSMutableArray object in a totally new memory location which has no business of having the previously saved data objects. Those objects were stored in a totally different NSMutableArray object(The one in the initial build). However when i %p the mapPoints in the NSLog function, there was difference in the memory addresses between the previous and current runs in the debugger area.
Pls have a look at the code snippet below—

self.mapPoints= [NSKeyedUnarchiver unarchiveObjectWithFile:path];//Shouldn't this method return the memory address of the object from the previous run?? NSLog(@"%p %@",self.mapPoints, self.mapPoints); if (!self.mapPoints) { self.mapPoints= [[NSMutableArray alloc] init]; }


#4

[quote]self.mapPoints= [NSKeyedUnarchiver unarchiveObjectWithFile:path];//Shouldn't this method return the memory address of the object from the previous run?? NSLog(@"%p %@",self.mapPoints, self.mapPoints); if (!self.mapPoints) { self.mapPoints= [[NSMutableArray alloc] init]; }[/quote][quote]//Shouldn’t this method return the memory address of the object from the previous run??[/quote]
No. If it did, it would be just a coincidence.


#5

[quote=“ibex10”]
No. If it did, it would be just a coincidence.[/quote]

Well the reality is that it doesn’t return the address of that initial NSMutableArray object. But thats what doesn’t seem right to me. I mean if, after rerunning the app when we have already saved the data objects that we intended to save, the object pointer of type NSMutableArray i.e. mapPoints (in my code) is made to reference whatever object is returned by the message— [NSKeyedUnarchiver unarchiveObjectWithFile:path]. That “whatever” object should be the same one as from the previous run (which was used to store all the data objects).
So with respect to that belief in mind, i’m confused as to why the message doesn’t return the same memory address as before. If it had, it would signify the same initial object.
Pls feel free to right me if i’m wrong on this…I could be wrong but this is my conviction.


#6

When an object is archived, what goes into the archive is not the address of the object but its value. When you unarchive that archive, you get the same object constructed from that value but its address will most likely be different from the address of the archived object.


#7

So you mean that the object that is being returned during the process of unarchiving, is the same one as the initial NSMutableArray object but it resides at a different memory address?
yeah i guess dynamic memory allocation is what happens during object instantiation.