Try to load a created Map in MKMapView - ios

I am trying to load my Map which I created with a kml File in Google-Maps. The Google Maps Link. I have to say it is only an example, but it is the same principle.
The easiest way is to load in a WebView, but that is ugly in my eyes.
Thank you for reading my Question!
Best regards CTS

To load a KML into a MKMapView:
Add the necessary frameworks (MapKit.framework and CoreLocation.framework) to your target;
Load and parse the KML;
Create your annotations on the basis of the KML; and
Set your map's region to encompass the annotations.
Thus, that might look like:
#import <MapKit/MapKit.h>
- (void)loadKml:(NSURL *)url
{
// parse the kml
Parser *parser = [[Parser alloc] initWithContentsOfURL:url];
parser.rowElementName = #"Placemark";
parser.elementNames = #[#"name", #"Snippet", #"coordinates", #"description"];
parser.attributeNames = nil;
[parser parse];
// add annotations for each of the entries
for (NSDictionary *locationDetails in parser.items)
{
MKPointAnnotation *annotation = [[MKPointAnnotation alloc] init];
annotation.title = locationDetails[#"name"];
annotation.subtitle = locationDetails[#"Snippet"];
NSArray *coordinates = [locationDetails[#"coordinates"] componentsSeparatedByString:#","];
annotation.coordinate = CLLocationCoordinate2DMake([coordinates[1] floatValue], [coordinates[0] floatValue]);
[self.mapView addAnnotation:annotation];
}
// update the map to focus on the region that encompasses all of your annotations
MKCoordinateRegion region;
if ([self.mapView.annotations count] > 1)
{
region = [self regionForAnnotations:self.mapView.annotations];
region = MKCoordinateRegionMake(region.center, MKCoordinateSpanMake(region.span.latitudeDelta * 1.05, region.span.longitudeDelta * 1.05)); // expand the region by 5%
}
else
{
id<MKAnnotation> annotation = self.mapView.annotations[0];
region = MKCoordinateRegionMakeWithDistance(annotation.coordinate, 100.0, 100.0);
}
[self.mapView setRegion:region animated:YES];
}
My Parser class is just a NSXMLParser subclass that I've written that will create an array of items one per occurrence of rowElementName, and for each row, it will grab the elements listed in the elementNames array.
Parser.h:
#import <Foundation/Foundation.h>
#interface Parser : NSXMLParser
#property (nonatomic, strong) NSString *rowElementName; // this is the element name that identifies a new row of data in the XML
#property (nonatomic, strong) NSArray *attributeNames; // this is the array of attributes we might want to retrieve for that element name
#property (nonatomic, strong) NSArray *elementNames; // this is the list of sub element names for which we're retrieving values
#property (nonatomic, strong) NSMutableArray *items; // after parsing, this is the array of parsed items
#end
Parser.m:
#import "Parser.h"
#interface Parser () <NSXMLParserDelegate>
#property (nonatomic, strong) NSMutableDictionary *item; // while parsing, this is the item currently being parsed
#property (nonatomic, strong) NSMutableString *elementValue; // this is the element within that item being parsed
#end
#implementation Parser
- (id)initWithContentsOfURL:(NSURL *)url
{
self = [super initWithContentsOfURL:url];
if (self)
{
self.delegate = self;
}
return self;
}
- (id)initWithData:(NSData *)data
{
self = [super initWithData:data];
if (self)
{
self.delegate = self;
}
return self;
}
- (id)initWithStream:(NSInputStream *)stream
{
self = [super initWithStream:stream];
if (self)
{
self.delegate = self;
}
return self;
}
#pragma mark - NSXMLParserDelegate methods
- (void)parserDidStartDocument:(NSXMLParser *)parser
{
self.items = [[NSMutableArray alloc] init];
if (!self.rowElementName)
NSLog(#"%s Warning: Failed to specify row identifier element name", __FUNCTION__);
}
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qualifiedName attributes:(NSDictionary *)attributeDict
{
if ([elementName isEqualToString:self.rowElementName])
{
self.item = [[NSMutableDictionary alloc] init];
for (NSString *attributeName in self.attributeNames)
{
id attributeValue = [attributeDict valueForKey:attributeName];
if (attributeValue)
[self.item setObject:attributeValue forKey:attributeName];
}
}
else if ([self.elementNames containsObject:elementName])
{
self.elementValue = [[NSMutableString alloc] init];
}
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
if (self.elementValue)
{
[self.elementValue appendString:string];
}
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
if ([elementName isEqualToString:self.rowElementName])
{
[self.items addObject:self.item];
self.item = nil;
}
else if ([self.elementNames containsObject:elementName])
{
[self.item setValue:self.elementValue forKey:elementName];
self.elementValue = nil;
}
}
#end
Finally, the only other utility method that my loadKml uses is regionForAnnotations, which defines a region based upon a series of annotations. Rob Mooney wrote a simple routine to do that:
- (MKCoordinateRegion)regionForAnnotations:(NSArray *)annotations {
CLLocationDegrees minLat = 90.0;
CLLocationDegrees maxLat = -90.0;
CLLocationDegrees minLon = 180.0;
CLLocationDegrees maxLon = -180.0;
for (id <MKAnnotation> annotation in annotations) {
if (annotation.coordinate.latitude < minLat) {
minLat = annotation.coordinate.latitude;
}
if (annotation.coordinate.longitude < minLon) {
minLon = annotation.coordinate.longitude;
}
if (annotation.coordinate.latitude > maxLat) {
maxLat = annotation.coordinate.latitude;
}
if (annotation.coordinate.longitude > maxLon) {
maxLon = annotation.coordinate.longitude;
}
}
MKCoordinateSpan span = MKCoordinateSpanMake(maxLat - minLat, maxLon - minLon);
CLLocationCoordinate2D center = CLLocationCoordinate2DMake((maxLat - span.latitudeDelta / 2), maxLon - span.longitudeDelta / 2);
return MKCoordinateRegionMake(center, span);
}

Related

How to feed existing markers into a marker cluster in GoogleMap iOS SDK?

I am developing an app using Google Map iOS SDK (Objective C).
Done so far:
Loaded markers from the server in map view
Generate random clusters only.
When I run the project it looks like this.
Screen shot of map view
It shows both markers and clusters both in the map view at zoom level 10. But I wanted to show clusters first then when I zoomed in it should show the real markers. Not the randomly generated markers that I created because I don't know a way to show the clusters in the map.
Here is the full code with fake URL link:
#import "ViewController.h"
//#import "CSMarker.h"
#import <GoogleMaps/GoogleMaps.h>
#import <Google-Maps-iOS-Utils/GMUMarkerClustering.h>
//importing POI Item object - points of interest
#interface POIItem : NSObject<GMUClusterItem>
#property(nonatomic, readonly) CLLocationCoordinate2D position;
#property(nonatomic, readonly) NSString *name;
- (instancetype)initWithPosition:(CLLocationCoordinate2D)position name:(NSString *)name;
#end
#implementation POIItem
- (instancetype)initWithPosition:(CLLocationCoordinate2D)position name:(NSString *)name {
if ((self = [super init])) {
_position = position;
_name = [name copy];
}
return self;
}
#end
//implementation start - map view controller
static const NSUInteger kClusterItemCount = 60;
static const double kCameraLatitude = 25.277683999999997;
static const double kCameraLongitude = 55.309802999999995;
#interface ViewController ()<GMUClusterManagerDelegate, GMSMapViewDelegate>
{
NSMutableArray *waypoints_;
NSMutableArray *waypointStrings_;
GMSMapView *_mapView;
GMUClusterManager *_clusterManager;
}
#property(strong, nonatomic) NSURLSession *markerSession;
#property(strong, nonatomic) GMSMapView *mapView;
#property(copy, nonatomic) NSSet *markers;
#property(nonatomic, strong) NSMutableArray *markersArray;
#end
#implementation ViewController
#synthesize gs;
- (void)viewDidLoad {
[super viewDidLoad];
GMSCameraPosition *camera = [GMSCameraPosition cameraWithLatitude:kCameraLatitude
longitude:kCameraLongitude
zoom:10];
self.mapView =
[GMSMapView mapWithFrame:self.view.bounds camera:camera];
[self.view addSubview:self.mapView];
self.mapView.settings.compassButton = YES;
self.mapView.settings.myLocationButton = YES;
//setup the cluster manager
NSURLSessionConfiguration *config =
[NSURLSessionConfiguration defaultSessionConfiguration];
config.URLCache = [[NSURLCache alloc] initWithMemoryCapacity:2 * 1024 * 1024
diskCapacity:10 * 1024 * 1024
diskPath:#"MarkerData"];
self.markerSession = [NSURLSession sessionWithConfiguration:config];
[self downloadMarkerData];
//cluster load setup
id<GMUClusterAlgorithm> algorithm = [[GMUNonHierarchicalDistanceBasedAlgorithm alloc] init];
id<GMUClusterIconGenerator> iconGenerator = [[GMUDefaultClusterIconGenerator alloc] init];
id<GMUClusterRenderer> renderer =
[[GMUDefaultClusterRenderer alloc] initWithMapView:self.mapView
clusterIconGenerator:iconGenerator];
_clusterManager =
[[GMUClusterManager alloc] initWithMap:self.mapView algorithm:algorithm renderer:renderer];
// Generate and add random items to the cluster manager.
[self generateClusterItems];
// Call cluster() after items have been added to perform the clustering and rendering on map.
[_clusterManager cluster];
// Register self to listen to both GMUClusterManagerDelegate and GMSMapViewDelegate events.
[_clusterManager setDelegate:self mapDelegate:self];
// Do any additional setup after loading the view, typically from a nib.
}
- (NSMutableArray *)markersArray
{
if (!_markersArray) {
_markersArray = [NSMutableArray array];
}
return _markersArray;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
//downloading marker data
- (void)downloadMarkerData {
NSURL *lakesURL =
[NSURL URLWithString:#"http://myscrap.com/xxx.php/webservice/xxx/xxxx"];
NSURLSessionDataTask *task = [self.markerSession dataTaskWithURL:lakesURL
completionHandler:^(NSData *data, NSURLResponse *response, NSError *e)
{
NSArray *json = [NSJSONSerialization JSONObjectWithData:data
options:0
error:nil];
NSLog(#"json: %#",json);
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self createMarkerObjectsWithJson:json];
}];
}];
[task resume];
}
-(void)drawMarkers
{
for(GMSMarker *marker in self.markers) {
if(marker.map == nil) {
marker.map = self.mapView;
}
}
}
-(void)createMarkerObjectsWithJson:(NSArray *)json
{
NSMutableSet *mutableSet = [[NSMutableSet alloc] initWithSet:self.markers];
for (NSDictionary *markerData in json) {
GMSMarker *newMarker = [[GMSMarker alloc] init];
// newMarker.appearAnimation = [markerData[#"id"] integerValue];
newMarker.position = CLLocationCoordinate2DMake([markerData[#"latitud"] doubleValue],
[markerData[#"longitude"] doubleValue]);
newMarker.title = markerData[#"name"];
newMarker.snippet = markerData[#"adress"];
// [mutableSet addObject:newMarker];
newMarker.map=self.mapView;
}
self.markers =[mutableSet copy];
[self drawMarkers];
}
#pragma mark GMUClusterManagerDelegate
- (void)clusterManager:(GMUClusterManager *)clusterManager didTapCluster:(id<GMUCluster>)cluster {
GMSCameraPosition *newCamera =
[GMSCameraPosition cameraWithTarget:cluster.position zoom:_mapView.camera.zoom + 1];
GMSCameraUpdate *update = [GMSCameraUpdate setCamera:newCamera];
[_mapView moveCamera:update];
}
#pragma mark GMSMapViewDelegate
- (BOOL)mapView:(GMSMapView *)mapView didTapMarker:(GMSMarker *)marker {
POIItem *poiItem = marker.userData;
if (poiItem != nil) {
NSLog(#"Did tap marker for cluster item %#", poiItem.name);
} else {
NSLog(#"Did tap a normal marker");
}
return NO;
}
#pragma mark Private
// Randomly generates cluster items within some extent of the camera and adds them to the
// cluster manager.
- (void)generateClusterItems {
const double extent = 0.2;
for (int index = 1; index <= kClusterItemCount; ++index) {
double lat = kCameraLatitude + extent * [self randomScale];
double lng = kCameraLongitude + extent * [self randomScale];
NSString *name = [NSString stringWithFormat:#"Item %d", index];
id<GMUClusterItem> item =
[[POIItem alloc] initWithPosition:CLLocationCoordinate2DMake(lat, lng) name:name];
[_clusterManager addItem:item];
}
}
// Returns a random value between -1.0 and 1.0.
- (double)randomScale {
return (double)arc4random() / UINT32_MAX * 2.0 - 1.0;
}
try changing in view did load
GMSCameraPosition *camera = [GMSCameraPosition cameraWithLatitude:kCameraLatitude longitude:kCameraLongitude zoom:4];
the zoom to a 5 or 4. It then should show the clusters and not the markers. When you zoom in, it will show the "real" markers.

XML Parsing goes to EXC_BAD_ACCESS code 1 address 0x10

So here is the thing. I am trying to build no-ARC/no-Storyboard project without previous experience in manual memory-management.
Full source code available here.
So what I've got here is a
class, which helps me create Products object with custom initialiser
Products.h
#interface Products : NSObject
#property (nonatomic,retain)NSString *productName;
#property (nonatomic,retain)NSString *productDescription;
#property (nonatomic,retain)NSString *productImage;
-(id) initWithName: (NSString *) name
description: (NSString *) description
image: (NSString *) image;
#end
Products.m
#implementation Products
-(id) initWithName:(NSString *)name description:(NSString *)description image:(NSString *)image {
self = [super init];
if (self) {
self.productName = name;
self.productDescription = description;
self.productImage = image;
}
return self;
#end
There you can see ProductParser class, which one contains most of magic
ProductsParser.h
#interface ProductsParser : NSObject <NSXMLParserDelegate>
#property (nonatomic,retain) NSMutableArray *productArray;
-(id) initWithArray:(NSMutableArray *) productArray;
-(void) parseXMLFile;
#end
ProductParser.m
// Creating private properties
#interface ProductsParser()
#property NSXMLParser *parser;
#property NSString *element;
#property NSMutableString *currentProductName;
#property NSMutableString *currentProductDescription;
#property NSMutableString *currentProductImage;
#end
#implementation ProductsParser
-(id) initWithArray:(NSMutableArray *)productArray {
self = [super init];
if (self) {
self.productArray = productArray;
}
return self;
}
-(void) parseXMLFile {
// We will do it here instead of writing that in viewDidLoad
NSURL *xmlPath = [[NSBundle mainBundle]URLForResource:#"productsList" withExtension:#"xml" ];
self.parser = [[NSXMLParser alloc]initWithContentsOfURL:xmlPath];
self.parser.delegate = self;
[self.parser parse];
[self.parser release];
}
-(void) parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict {
self.element = elementName;
}
-(void) parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
string = [string stringByReplacingOccurrencesOfString:#"\n" withString:#""];
string = [string stringByReplacingOccurrencesOfString:#"\t" withString:#""];
string = [string stringByReplacingOccurrencesOfString:#" " withString:#""];
if ([self.element isEqualToString:#"Name"]) {
if (self.currentProductName == nil) {
self.currentProductName = [[NSMutableString alloc] initWithString:string];
} else {
[self.currentProductName appendString:string];
}
}
if ([self.element isEqualToString:#"Description"]) {
if (self.currentProductDescription == nil) {
self.currentProductDescription = [[NSMutableString alloc] initWithString:string];
} else {
[self.currentProductDescription appendString:string];
}
} if ([self.element isEqualToString:#"Image"]) {
if (self.currentProductImage == nil) {
self.currentProductImage = [[NSMutableString alloc] initWithString:string];
} else {
[self.currentProductImage appendString:string];
}
}
}
-(void) parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
if ([elementName isEqualToString:#"Product"]) {
Products *thisProduct = [[Products alloc] initWithName:self.currentProductName description:self.currentProductDescription image:self.currentProductImage];
[self.productArray addObject:thisProduct];
[self.currentProductName release];
self.currentProductName = nil;
[self.currentProductDescription release];
self.currentProductDescription = nil;
[self.currentProductImage release];
self.currentProductImage = nil;
[thisProduct release];
thisProduct = nil;
[self.element release];
self.element = nil;
}
}
#end
Later on, in the class which is suppose to handle the UITableView I am creating:
#property (retain,nonatomic)NSMutableArray *productArray;
And In viewDidLoad we have this code
self.productArray = [[NSMutableArray alloc] init];
ProductsParser *menuParser = [[ProductsParser alloc] initWithArray:self.productArray];
[menuParser parseXMLFile];
[menuParser release];
All that results in EXC_BAD_ACCESS pointing to
#property (nonatomic,retain)NSString *productName;
I've done some research about that error. Users claims that they successfully getting rid of that error by simply calling properties with self. syntax.(which is suppose to increase retain count?) In my case it is not the issue, as you may see.(Or did I missed something?)
I am also can see in debugger that productArray which is suppose to be populated with my products from .xml file, is in fact populated by trash stuff like #"/n/t" #"/n " #"/n/t" and so on.(why why why?!)
So at least point it to me, where to start. Really appreciate your guys help.
UPDATE 1: Apparently there was some defect logic, I almost got rid of the \n\t stuff by changing code in foundCharacters section.
So now instead of just \n\t \n \n\t I got actual data in my array with those afterwards. Like so:
#"Box of chocolate \n\t\t"
#"Box of chocolate, very tasty \n"
#"ImageName \n\t"
I know that \n should be like new line, and \t is apparently tabulation. Still, don't have a clear idea on how to completely get rid of those and why are they popping up.
UPDATE 2: I manage to get rid of the trashy \n\t from the array by adding stringByReplacingOccurrencesOfString. (code updated)

SIGABRT error implementing MKAnnotation protocol

I've been trying to do a simple app using MKMapView, and I'm getting hit with a SIGABRT error upon trying to call the <MKAnnotation> class.
The DetailViewController.m file:
#import "WVTDetailViewController.h"
#import <MapKit/MapKit.h>
#import <AddressBook/AddressBook.h>
#import "Pin.h"
#interface WVTDetailViewController ()
{
CLLocationCoordinate2D location;
NSMutableDictionary *masterDict;
NSDictionary *googleDict;
NSDictionary *yahooDict;
NSDictionary *appleDict;
NSDictionary *microsoftDict;
NSDictionary *facebookDict;
}
- (void)configureView;
#end
#implementation WVTDetailViewController
#synthesize address = _address;
- (void)configureView
{
// Update the user interface for the detail item.
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self configureView];
[_mapView setShowsUserLocation:YES];
CLLocationCoordinate2D googleLocation;
googleLocation.latitude = 37.4221;
googleLocation.longitude = -122.0844;
// Yahoo:
CLLocationCoordinate2D yahooLocation;
yahooLocation.latitude = 37.417354;
yahooLocation.longitude = -122.025189;
// Apple:
CLLocationCoordinate2D appleLocation;
appleLocation.latitude = 37.332313;
appleLocation.longitude = -122.030746;
// Microsoft:
CLLocationCoordinate2D microsoftLocation;
microsoftLocation.latitude = 47.639764;
microsoftLocation.longitude = -122.128435;
// Facebook:
CLLocationCoordinate2D facebookLocation;
facebookLocation.latitude = 37.483489;
facebookLocation.longitude = -122.149542;
if ([_address isEqualToString:#"Google"])
{
location = googleLocation;
}
if ([_address isEqualToString:#"Yahoo"])
{
location = yahooLocation;
}
if ([_address isEqualToString:#"Apple"])
{
location = appleLocation;
}
if ([_address isEqualToString:#"Microsoft"])
{
location = microsoftLocation;
}
if ([_address isEqualToString:#"Facebook"])
{
location = facebookLocation;
}
googleDict = #{(NSString *)kABPersonAddressStreetKey: #"1600 Amphitheatre Pkwy",
(NSString *)kABPersonAddressCityKey: #"Mountain View",
(NSString *)kABPersonAddressStateKey: #"CA",
(NSString *)kABPersonAddressZIPKey: #"94043"
};
yahooDict = #{(NSString *)kABPersonAddressStreetKey: #"701 1st Ave",
(NSString *)kABPersonAddressCityKey: #"Sunnyvale",
(NSString *)kABPersonAddressStateKey: #"CA",
(NSString *)kABPersonAddressZIPKey: #"94089"
};
appleDict = #{(NSString *)kABPersonAddressStreetKey: #"1 Infinite Loop",
(NSString *)kABPersonAddressCityKey: #"Cupertino",
(NSString *)kABPersonAddressStateKey: #"CA",
(NSString *)kABPersonAddressZIPKey: #"95014"
};
microsoftDict = #{(NSString *)kABPersonAddressStreetKey: #"One Microsoft Way",
(NSString *)kABPersonAddressCityKey: #"Redmond",
(NSString *)kABPersonAddressStateKey: #"WA",
(NSString *)kABPersonAddressZIPKey: #"98052"
};
facebookDict = #{(NSString *)kABPersonAddressStreetKey: #"1 Hacker Way",
(NSString *)kABPersonAddressCityKey: #"Menlo Park",
(NSString *)kABPersonAddressStateKey: #"CA",
(NSString *)kABPersonAddressZIPKey: #"94025"
};
masterDict = [[NSMutableDictionary alloc] init];
[masterDict setObject: googleDict forKey: #"Google"];
[masterDict setObject: yahooDict forKey: #"Yahoo"];
[masterDict setObject: appleDict forKey: #"Apple"];
[masterDict setObject: microsoftDict forKey: #"Microsoft"];
[masterDict setObject: facebookDict forKey: #"Facebook"];
}
- (void)viewWillAppear:(BOOL)animated
{
// Establish a 1.5km "square" around the 2D coordinate "location" and display it
MKCoordinateRegion viewRegion = MKCoordinateRegionMakeWithDistance(location, 1500, 1500);
[_mapView setRegion:viewRegion animated:YES];
Pin *myLocationPin = [[Pin alloc] initWithNameAndCoords: _address coords: location];
myLocationPin.addr = [masterDict objectForKey:_address];
[_mapView addAnnotation: myLocationPin];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
and the Pin.m file (the one implementing <MKAnnoation>):
#import "Pin.h"
#import <AddressBook/AddressBook.h>
#implementation Pin
- (id) initWithNameAndCoords: (NSString *) name coords: (CLLocationCoordinate2D) coords
{
if (self = [super init])
{
_name = name;
_coords = coords;
}
return self;
}
- (NSString *)title
{
return _name;
}
- (MKMapItem*) returnMapItem
{
MKMapItem *mapItem = [[MKMapItem alloc] initWithPlacemark:[[MKPlacemark alloc] initWithCoordinate:_coords addressDictionary: _addr]];
mapItem.name = _name;
return mapItem;
}
#end
First off, the SIGABRT error is situated at the [_mapView addAnnotation: myLocationPin]; line. I'm also getting a warning in Pin.m saying that Auto property synthesis will not synthesize property declared in a protocol. I used the #synthesize directive to force synthesis.
I've looked through the debugger output, though, and that doesn't seem to be an issue - the synthesized properties are receiving values as they should be.
You should add this to getter function in Pin.m
-(CLLocationCoordinate2D)coordinate { return _coord; }

Encoding c-struct with Mantle (NSCoding)

I want to use Mantle framework (https://github.com/github/Mantle) to support NSCoding for my class with struct property:
typedef struct {
int x;
int y;
} MPoint;
typedef struct {
MPoint min;
MPoint max;
} MRect;
#interface MObject : MTLModel
#property (assign, nonatomic) MRect rect;
#end
#implementation MObject
#end
But when I tried to [NSKeyedArchiver archiveRootObject:obj toFile:#"file"]; its crashed in MTLModel+NSCoding.m, in - (void)encodeWithCoder:(NSCoder *)coder on line
case MTLModelEncodingBehaviorUnconditional:
[coder encodeObject:value forKey:key];
Does Mantle supports c-struct encoding (and also decoding) or I've need to custom implementing NSCoding protocol for such classes?
My original data structure is an XML (yeah, I know):
...
<Lat>32.062883</Lat>
<Lot>34.782904</Lot>
...
I used MTLXMLAdapter based on KissXML, but you can see how it's applicable to any other serializer.
+ (NSValueTransformer *)coordinateXMLTransformer {
return [MTLValueTransformer reversibleTransformerWithBlock:^id(NSArray *nodes) {
CLLocationCoordinate2D coordinate;
for (DDXMLNode *node in nodes) {
if ([[node name] isEqualToString:#"Lat"]) {
coordinate.latitude = [[node stringValue] doubleValue];
} else if ([[node name] isEqualToString:#"Lot"]) {
coordinate.longitude = [[node stringValue] doubleValue];
}
}
return [NSValue value:&coordinate
withObjCType:#encode(CLLocationCoordinate2D)];
}];
}
You can add a reverseBlock if needed.
It was easier than I thought:
Exclude property in +encodingBehaviorsByPropertyKey
Manual encode/encode excluded property
Sample:
#pragma mark - MTLModel + NSCoding
- (id)initWithCoder:(NSCoder *)coder {
self = [super initWithCoder:coder];
if (self) {
self.rect = [[self class] mRectFromData:[coder decodeObjectForKey:#"rectData"]];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder {
[super encodeWithCoder:coder];
[coder encodeObject:[[self class] dataFromMRect:self.rect] forKey:#"rectData"];
}
+ (NSDictionary *)encodingBehaviorsByPropertyKey {
NSDictionary *excludeProperties = #{
NSStringFromSelector(#selector(rect)): #(MTLModelEncodingBehaviorExcluded)
};
NSDictionary *encodingBehaviors = [[super encodingBehaviorsByPropertyKey] mtl_dictionaryByAddingEntriesFromDictionary:excludeProperties];
return encodingBehaviors;
}
#pragma mark - MRect transformations
+ (MRect)mRectFromData:(NSData *)rectData {
MRect rect;
[rectData getBytes:&rect length:sizeof(rect)];
return rect;
}
+ (NSData *)dataFromMRect:(MRect)rect {
return [NSData dataWithBytes:&rect length:sizeof(rect)];
}

Class with memory leaks in instruments

I have a couple of classes which functions are execute SQL statements against a WEB service in order to get or set data in the database.
Works fine, but the problem is when I'm testing in instruments/leaks the 90% of the leaks are because these classes.
Could you tell me what I'm loosing?
Thanks.
Here is the code:
Where the data is stored:
.h
#interface iSQLResult : NSObject {
NSMutableArray *Records;
}
#property (nonatomic, assign) int CountX;
#property (nonatomic, assign) int CountY;
#property (nonatomic, retain) NSMutableArray *Columns;
#property (nonatomic, retain) NSMutableArray *Records;
#property (nonatomic, retain) NSMutableArray *FieldsNames;
#property (nonatomic, assign) int ErrorCode;
#property (nonatomic, retain) NSString *ErrorDescription;
-(void)addField:(NSString*)fieldName;
-(void)addRecord:(NSMutableArray*)items;
-(NSMutableArray *)getRecord:(int)y;
-(NSString*)getValue:(int) x posY:(int) y;
-(NSString*)getValueByName:(NSString *) colName posY:(int) y;
-(void)setValueByName:(NSString *) colName posY:(int) y value:(NSString *)value;
-(id) copyWithZone: (NSZone *) zone;
#end
.m
#import "iSQLResult.h"
#import <stdarg.h>
#implementation iSQLResult
#synthesize CountX;
#synthesize CountY;
#synthesize Columns;
#synthesize Records;
#synthesize FieldsNames;
#synthesize ErrorCode;
#synthesize ErrorDescription;
-(id)init
{
self = [super init];
if (self)
{
self.CountX =0;
self.CountY =0;
self.ErrorCode = 0;
self.ErrorDescription = #"";
self.FieldsNames = [NSMutableArray array];
self.Columns = [NSMutableArray array];
self.Records = [NSMutableArray array];
}
return self;
}
-(void)removeRecord:(int)index
{
[self.Records removeObjectAtIndex:index];
self.CountY = self.CountY - 1;
}
-(void)addField:(NSString*)fieldName
{
[self.FieldsNames addObject:[NSString stringWithFormat:#"%#", fieldName]];
self.CountX = self.CountX +1;
}
-(void)addRecord:(NSMutableArray*)items
{
[self.Records addObject:items];
self.CountY = self.CountY +1;
}
-(NSMutableArray *)getRecord:(int)y
{
return [Records objectAtIndex:y];
}
-(NSString *)getValue:(int) x posY:(int)y
{
return [[NSString stringWithFormat:#"%#", [[Records objectAtIndex:y] objectAtIndex:x]] copy];
}
-(NSString*)getValueByName:(NSString *) colName posY:(int) y
{
int a=0;
for (a=0;a<CountX;a++)
{
if ([[colName uppercaseString] isEqualToString:[[FieldsNames objectAtIndex:a] uppercaseString]])
{
return [[NSString stringWithFormat:#"%#", [[Records objectAtIndex:y] objectAtIndex:a]] copy];
}
}
return #"";
}
-(void)setValueByName:(NSString *) colName posY:(int) y value:(NSString *)value
{
int a=0;
for (a=0;a<CountX;a++)
{
if ([[colName uppercaseString] isEqualToString:[[FieldsNames objectAtIndex:a] uppercaseString]])
{
[[Records objectAtIndex:y] replaceObjectAtIndex:a withObject:value];
}
}
}
-(void)dealloc
{
[Columns release];
[Records release];
[FieldsNames release];
[ErrorDescription release];
[super dealloc];
}
-(id) copyWithZone: (NSZone *) zone
{
iSQLResult *SQLRCopy = [[iSQLResult allocWithZone: zone] init];
[SQLRCopy setCountX:self.CountX];
[SQLRCopy setCountY:self.CountY];
[SQLRCopy setRecords:[self.Records mutableCopyWithZone:zone]];
[SQLRCopy setColumns:[self.Columns mutableCopyWithZone:zone]];
[SQLRCopy setFieldsNames:[self.FieldsNames mutableCopyWithZone:zone]];
[SQLRCopy setErrorCode:self.ErrorCode];
[SQLRCopy setErrorDescription:[self.ErrorDescription copyWithZone:zone]];
return SQLRCopy;
}
#end
The class who ask for the data to the web service comunication class and gets a xml structure:
.h
#import <Foundation/Foundation.h>
#import "iSQLResult.h"
#import "IM2_WebServiceComm.h"
#interface iSQL : NSObject <NSXMLParserDelegate> {
iSQLResult *SQLR;
IM2_WebServiceComm * WSC;
NSXMLParser *xmlParser;
BOOL Found;
BOOL FieldsReaded;
BOOL loadFieldsNow;
BOOL loadErrNumNow;
BOOL loadErrDesNow;
NSString *chunksString;
NSMutableArray *tempRecord;
}
#property (nonatomic, retain) NSString *URLConnection;
-(void)SQLReader:(NSString*)SQLString;
-(void)SQLExec:(NSString*)SQLString;
-(void)setURLConnection:(NSString *) WebSURL;
-(iSQLResult*) getSQLR;
-(void) parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName;
-(void) parser:(NSXMLParser *) parser foundCharacters:(NSString *)string;
-(void) parser:(NSXMLParser *) parser didStartElement:(NSString *) elementName namespaceURI:(NSString *) namespaceURI qualifiedName:(NSString *) qName attributes:(NSDictionary *) attributeDict;
#end
.m
#import "iSQL.h"
#implementation iSQL
#synthesize URLConnection;
- (iSQLResult*)getSQLR
{
return [SQLR copy];
}
-(void)SQLExec:(NSString*)SQLString
{
FieldsReaded = NO;
Found = NO;
loadFieldsNow = NO;
if (SQLR)
{
[SQLR release];
SQLR = nil;
}
SQLR = [[iSQLResult alloc] init];
WSC = [[IM2_WebServiceComm alloc] init];
[WSC setURL:URLConnection];
NSString *theXML = [WSC callMethod:#"ExecNonQuery" sendInstruction:SQLString];
#try
{
xmlParser = [[NSXMLParser alloc] initWithData:[theXML dataUsingEncoding:NSUTF8StringEncoding]];
[xmlParser setDelegate: self];
[xmlParser setShouldResolveExternalEntities:NO];
if(![xmlParser parse])
{
NSLog(#"ERROR PARSING");
}
[xmlParser release];
}
#catch(NSException * ex)
{
NSLog(#"%#",[[ex reason] UTF8String]);
}
[WSC release];
}
-(void)SQLReader:(NSString*)SQLString
{
FieldsReaded = NO;
Found = NO;
loadFieldsNow = NO;
if (SQLR)
{
[SQLR release];
SQLR = nil;
}
SQLR = [[iSQLResult alloc] init];
WSC = [[IM2_WebServiceComm alloc] init];
[WSC setURL:URLConnection];
NSString *theXML = [WSC callMethod:#"ExecSQL" sendInstruction:SQLString];
#try
{
xmlParser = [[NSXMLParser alloc] initWithData:[theXML dataUsingEncoding:NSUTF8StringEncoding]];
[xmlParser setDelegate: self];
[xmlParser setShouldResolveExternalEntities:NO];
if(![xmlParser parse])
{
NSLog(#"ERROR PARSING");
}
[xmlParser release];
}
#catch(NSException * ex)
{
NSLog([NSString stringWithFormat:#"%#",[[ex reason] UTF8String]]);
}
[WSC release];
}
-(iSQLResult *)getSingleSQLR:(iSQLResult *)SQLSource usingRow:(int)y
{
iSQLResult *SQLRAux = [[[iSQLResult alloc]init]retain];
[SQLRAux setCountX:SQLSource.CountX];
[SQLRAux addRecord:[SQLSource getRecord:y]];
[SQLRAux setFieldsNames:SQLSource.FieldsNames];
return SQLRAux;
}
-(void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError{
NSLog(#"Error on XML Parse: %#", [parseError localizedDescription] );
}
//#pragma XML Parser Delegate Methods
-(void) parser:(NSXMLParser *) parser didStartElement:(NSString *) elementName namespaceURI:(NSString *) namespaceURI qualifiedName:(NSString *) qName attributes:(NSDictionary *) attributeDict
{
if (!chunksString)
{
chunksString = [[NSString alloc] init];
}
chunksString = #"";
if ([elementName isEqualToString:#"ErrCode"])
{
loadErrNumNow = YES;
}
if ([elementName isEqualToString:#"ErrDesc"])
{
loadErrDesNow = YES;
}
if ([elementName isEqualToString:#"Table"])
{
Found = YES;
loadFieldsNow = NO;
tempRecord = [[NSMutableArray alloc] init];
}
if (Found && ![elementName isEqualToString:#"Table"])
{
loadFieldsNow = YES;
if (!FieldsReaded)
{
[SQLR addField:elementName];
}
}
}
-(void) parser:(NSXMLParser *) parser foundCharacters:(NSString *)string
{
chunksString = [chunksString stringByAppendingString:string];
}
-(void) parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
NSString * finalString;
finalString = [NSString stringWithFormat:#"%#",chunksString];
if (loadErrNumNow)
{
[SQLR setErrorCode:[finalString intValue] ];
loadErrNumNow = NO;
}
if (loadErrDesNow)
{
[SQLR setErrorDescription:finalString];
loadErrDesNow = NO;
}
if (Found)
{
if (loadFieldsNow)
{
[tempRecord addObject:finalString];
}
}
if ([elementName isEqualToString:#"Table"])
{
[SQLR addRecord:tempRecord];
[tempRecord release];
Found = NO;
FieldsReaded = YES;
loadFieldsNow = NO;
}
}
-(void)dealloc
{
if (SQLR)
{
[SQLR release];
SQLR = nil;
}
[URLConnection release];
[super dealloc];
}
#end
This is an absolutely buggy class because the leaks, every access is a leak :(
Any help please?
Here are some things I notice:
-[iSQLResult copyWithZone:]
You're sending the result of mutableCopyWithZone: (which returns a retained object) to the setters, which retain the object again. One way to fix it is to autorelease the copy:
[SQLRCopy setRecords:[[self.Records mutableCopyWithZone:zone] autorelease]]
-[iSQL SQLExec:] and -[iSQL SQLReader:]
The ivars WSC and xmlPareser are alloced then released but not set to nil. This isn't a leak, but you're keeping a reference to a released object. If you dereference that, you'll crash.
-[iSQL getSingleSQLR:usingRow:]
iSQLResult *SQLRAux = [[[iSQLResult alloc]init]retain];
Did you mean autorelease instead of retain there? (And if you did, shouldn't you also autorelease the return in getSQLR, for consistency?)
-[iSQL parser:didStartElement:namespaceURI:qualifiedName:attributes:]
chunksString is alloced (+1 retain), but later in parser:foundCharacters: you're losing the reference (i.e., leaking the string) and replacing it with an autoreleased string.
That's all I notice off hand. It sounds like you're leaking more than just those objects, so I'm guessing you're leaking iSQL and/or iSQLResult objects, too. Remember, when you leak an object, you also leak everything those objects hold a reference to. Even if these classes were leak-free, if the code that's instantiating the classes isn't properly releasing the objects you'll see all the members in these classes leak, too. When fixing leaks, always look for "top-level" objects first.

Resources