organizing too many arrays Xcode objc - ios

I am working on an app that is going to have over 50 arrays with over 100 items in each array. They are used when certain conditions apply. Is there any way to put these on different pages and import them as needed. Here is a sample of my code.
if([washington isKindOfClass:[MKPolygon class]]){
MKPolygon *polygons = (MKPolygon*) washington;
CGMutablePathRef mpr = CGPathCreateMutable();
MKMapPoint *polygonPoints = polygons.points;
for (int p=0; p < polygons.pointCount; p++){
MKMapPoint mp = polygonPoints[p];
if (p == 0)
CGPathMoveToPoint(mpr, NULL, mp.x, mp.y);
else
CGPathAddLineToPoint(mpr, NULL, mp.x, mp.y);
}
if(CGPathContainsPoint(mpr , NULL, mapPointAsCGP, TRUE)){
citiesArray10000 = [NSArray arrayWithObjects:
#"47.620499&-122.350876&187&Seattle Washington",
#"47.673554&-117.416595&1843&Spokane Washington",
#"47.252199&-122.459832&&Tacoma Washington",
#"45.637236&-122.596516&&Vancouver Washington",
#"47.597839&-122.156489&&Bellevue Washington",
#"47.385318&122.2169290&&Kent Washington",
#"48.003267&-122.174223&&Everett Washington",
#"47.476075&-122.192026&&Renton Washington",
#"47.308837&-122.336104&&Federal Way Washington",nil;
there are 50 states thus 50 if statements and there are going to be 6 or more arrays per state and as many as 100 to 200 entries in each array.
This makes for a very extensive view controller page with all this information. Is there to put each state's arrays on one page and somehow call them when needed. Sorry this is probably simple but I don't know how to do it.

Put the data in plist files stored in your app's bundle. Then you can load each plist file as needed. Don't hardcode that much data in your code. It takes forever to compile and it is hard to read your code.
You might want to write some sort of data model class that encapsulates the data. Then you view controller can create an instance of the data model specifying what data is needed. The data model class can take care of loading the correct data as needed.

you can use MVC architecture for your code. Keep only the code that changes the view that will be visible to user such as changing textfields and labels in viewController and write all other functions in different file and call those functions as needed. Like creating points and getting information can be done on other files as model to this view controller.

It looks like the best practice in this case is to use SQLlite or some other local database to maintain such data.

Related

Make sure async methods execute in same order as they were called

Let's say I have 3 async methods which all do the same thing: send some data to a server and add the response data to an array (all of them add to the same array).
I call these methods at the same time, but they send different amounts of data to the server.
If the first method sends more data to the server than the third method, the third method will get the response back sooner, thus add the data to the array sooner. The response data consits of coordinates, so the order in which they are in the array matters.
How can I make sure, that even if the third method gets the repsonse sooner than the first or the second one, it won't add the data to the array before the previous methods did? Thus, preserving the order of the coordinates in the array.
The methods are NSURLConnections and they all send an async request.
EDIT: Here is the working code:
//Array of snapped points from the JSON
NSArray *snappedPoints = [result objectForKey:#"snappedPoints"];
NSMutableArray *locationsArray = [[NSMutableArray alloc] init];
//Loop through the snapped points array and add each coordinate to a temporary array
for (int i = 0; i<[snappedPoints count]; i++) {
NSDictionary *location = [[snappedPoints objectAtIndex:i] objectForKey:#"location"];
double latitude = [[location objectForKey:#"latitude"] doubleValue];
double longitude = [[location objectForKey:#"longitude"] doubleValue];
CLLocation *loc = [[CLLocation alloc] initWithLatitude:latitude longitude:longitude];
[locationsArray addObject:loc];
}
//Add these temporary location arrays to the dictionary with the key as the request number
[tempLocationDictionary setObject:locationsArray forKey:[NSString stringWithFormat:#"%i",requestNumber]];
//If all requests have completed get the location arrays from the dicitonary in the same order as the request were made
if([tempLocationDictionary count] == numberOfRequests){
//Just a path because I am drawing these coordinates on a map later
GMSMutablePath *path = [GMSMutablePath path];
//Loop through the dictionary and get the location arrays in the right order
for (int i = 0; i<[tempLocationDictionary count]; i++) {
//Create a dummy array
NSArray *array = [tempLocationDictionary objectForKey:[NSString stringWithFormat:#"%i",i+1]];
//Get the coordinates from the array which we just got from the dictionary
for (CLLocation *location in array) {
[path addCoordinate:location.coordinate];
}
}
One way would be to replace NSURLConnection with NSURLSession and its related classes. It's newer and generally a superior choice. For your specific situation, it's possible to use a custom NSURLSessionConfiguration where you would set HTTPMaximumConnectionsPerHost to 1. That way the connections would be forced to run in order, solving your problem.
Of course, not having simultaneous connections might slow things down. In that case you'll have to accumulate the data temporarily without adding it to your array and only update the array when all the connections are complete. There are various ways you might do this depending on exactly what data comes back from the server.
One relatively simple way: If you know you'll always have three connections, use an NSMutableDictionary with integer NSNumber objects as the keys. After each connection you'd do something like
mutableDictionary[#1] = // your server data here
Use #2 or #3 for the other connections. Every time you add data, check to see if you have results for all three, and if so, add everything to your array. There are lots of other ways around this, the key is to have some kind of temporary structure where you can (a) accumulate data until all the connections are complete, and (b) keep track of which data came from which connection, either simply by number, or by URL, or by some other unique data that the server provides.
This is a problem to be solved with NSOperationQueue and dependencies.
Check out Advanced NSOperations [transcript] from WWDC 2015:
When the WWDC app starts up, there's a bunch of setup that we need to do.
First, we are going to download a small configuration file, and this file will tell us small things like what's the most recently supported version of the application, what features we have enabled, and so on.
So after we download this file, we are going to perform a version check to make sure that you are running the latest version of the WWDC app.
And then after we check the version of the app, we can start downloading useful pieces of information, such as the news that we show in the News tab and the schedule for the conference.
After we've downloaded the schedule, then we can start importing any favorites that you've saved to iCloud, any feedback that you've submitted so you can see it in the app, and we are also going to start downloading the list of videos.
Applying dependencies as they describe for these operations should sort you nicely.

Route-me: placing a new route on a map

I have an app that uses Route-me with static maps (tiles stored in a sqlite database). The code below correctly adds a route to the map. However, I would like to have the app remove the route shown and add another route. In the code, the function gets a new set of route points (the variable pathNodes) in this line (the [route createRoute:whichRoute] method pulls the datapoints from the sqlite database - this works correctly):
pathNodes = [route createRoute:whichRoute];
Then I remove the existing layer in this code:
// remove the current layer, which holds the previous route
if (currentLayer != nil) {
[mapView.contents.overlay removeSublayer:currentLayer];
}
and add a new layer in this:
[mapView.contents.overlay addSublayer:(CALayer *)walkRoute.path];
For the first route created, this works perfectly. But when a new route is chosen, the existing layer gets removed and the new sublayer is added to the mapView, but is never shown.
It appears to me that this is just a matter of getting the mapView to redraw itself with the new sublayer, but I've tried everything i can think of or discover elsewhere, and nothing has worked.
How do I get the mapView to redraw itself. Or is there another problem that I'm not seeing?
All help will be greatly appreciated!
- (void) setRoute {
NSMutableArray *pathNodes;
int whichRoute = delegate.selectedRoute; // delegate.whichRoute will be an integer denoting which route the user has chosen
Route *route = [[Route alloc] init];// Route stores information about the route, and a way to create the path nodes from a sqlite database
pathNodes = [route createRoute:whichRoute];
CMRoute *walkRoute = [[CMRoute alloc] initWithNodes:pathNodes forMap:mapView];
// remove the current layer, which holds the previous route
if (currentLayer != nil) {
[mapView.contents.overlay removeSublayer:currentLayer];
}
[mapView.contents.overlay addSublayer:(CALayer *)walkRoute.path];
currentLayer = (CALayer *)walkRoute.path;
// set the map's center point, whkch is the startPointlat and long stored in route
CLLocationCoordinate2D demoCoordinate;
demoCoordinate.longitude = route.startPoint.longitude;
demoCoordinate.latitude = route.startPoint.latitude;
[mapView setNeedsDisplay];
[mapView moveToLatLong:demoCoordinate];
}
In case anyone is still looking at this...
I never got a RouteMe map to redraw a route when the route changed. However - and this is a big however - iOS 8 and 9 have all the necessary capabilities to do static maps and draw and re-draw routes. And it all works. I re-wrote the entire app in Swift using UIMapKit and it all works much better. Anyone thinking about using RouteMe - I'd recommend thinking twice and three times and four times about doing that. RouteMe is not being maintained and is just plain buggy in some key areas. (I looked at the source code once to see what the heck was going on and found comments that there was some legacy code in the class that no one could figure out why it was there, but if removed, broke the class.)
UIMapKit - that's the answer.

Exporting large amounts of data from coredata to json

I'm trying to export some data from core data to JSON. While the record count isn't particularly large (around 5000-15000 records), my data model is complex and there is a large amount of data in each record, so when I export this I exceed the allowable memory and iOS kills my app.
The steps i currently take are:
1. I have a method that extracts all the data from cordata and stores it an `NSDictionary`
2. I then write it to a file using an `NSOutputStream` and `NSJSONSerialization`
3. I then zip up the file and send it via email
I'm pretty sure that steps 2 and 3 are fine from a max memory perspective as I stream the data. But the problem is that it gets killed in step 1 because I'm effectively pulling all the data out of CD and putting it in memory so I can pass it through NSOutputStream to NSJSONSerialization.
Anyone know how to not have to pull everything into memory, but still write to a single tree JSON file?
Update - More Detail
My data structure (simplified for clarify) looks like this.
Given its not just a flat set of records but a hierarchal structure of objects with relationships i cant figure out how to pull the data out of core data in batches and fed tot he json streamer rather than all in memory to construct the json. my step one above is actually a collection of recursive methods that pull the data out of the core data entities and construct the 'NSDictionary'.
Folder {
Folder {
Word {
details type 1
details type 2
}
Word {
details type 1
details type 2
}
}
Folder {
Word {
details type 1
details type 2
}
Word {
details type 1
details type 2
}
}
Word {
details type 1
details type 2
}
}
[UPDATED TO IMPLEMENT LOW MEMORY SERIAL OUTPUT OF NESTED FOLDER HIERARCHY AS NESTED JSON OBJECT FILE]
Now you have provided more detail it's clear the original problem statement lacked sufficient detail for anyone to be able to provide an answer for you. Your issue is actually an age-old problem of how to traverse hierarchies in a memory efficient way combined with the fact the iOS JSON Library is quite light and doesn't easily support streamed writing of deep hierarchies).
The best approach is to use a technique known as the visitor pattern. For each of your NSManagedObject types shown above, implement a protocol called visitor, e.g. just the interface line for each object should look something like this:
#interface Folder : NSManagedObject <Visitable>
#interface Word : NSManagedObject <Visitable>
The visitor protocol should define a method call for all objects that comply with the protocol.
#protocol Visitable <NSObject>
- (void)acceptVisitor:(id<Visitor>)visitor;
#end
You are going to define a visitor object, which itself implements a visitor protocol.
#protocol Visitor <NSObject>
- (void)visitFolder:(Folder*)folder;
- (void)visitWord:(Word*)word;
#end
#interface JSONVisitor : NSObject <Visitor>
#property (nonatomic, strong) NSURL *streamURL;
- (void)startVisiting:(id<Visitable>)visitableObject;
#end
#implementation JSONVisitor
#property (nonatomic, strong) NSOutputStream *outputStream;
- (void)startVisiting:(id<Visitable>)visitableObject
{
if ([visitableObject respondsToSelector:#selector(acceptVisitor:)]
{
if (_outputStream == nil)
{
// more code required set up your output stream
// specifically as a JSON output stream.
// add code to either set the stream URL here,
// or set it when the visitor object is instantiated.
_outputStream = [NSOutputStream outputStreamWithURL:_streamURL append:YES];
}
[_outputStream open];
// Note 1a Bypass Apple JSON API which doesn't support
// writing of partial objects (doing so is very easy anyway).
// Write opening root object fragment text string to stream
// such as:
// {
// "$schema" : "http://myschema.com/draft-01/schema#Folder1",
// "name" : "Folder export",
// "created" : "2013-07-16T19:20:30.45+01:00",
// "Folders" : [
[visitableObject acceptVisitor:self];
// Note 1b write closing JSON root object
// e.g.
// ]
// }
[_outputStream close];
}
}
- (void)visitFolder:(Folder*)folder
{
// Note 2a Bypass Apple JSON API which doesn't appear to support
// writing of partial objects (Writing JSON is very easy anyway).
// This next step would be best done with a proper templating system,
// but for simplicity of illustration I'm suggesting writing out raw
// JSON object text fragments.
// Write opening JSON Folder object fragment text string to stream
// e.g.
// "Folder" : {
if ([folder.folders count] > 1) {
// Write opening folder array fragment to stream e.g.
// "Folders" : [
// loop through folder member NSManagedObjects here
// (note defensive checks for nulls not included).
NSUInteger count = 0;
for (Folder *nestedFolder in folder.folders)
{
if (count > 0) // print comma to output stream
[nestedFolder acceptVisitor:self];
count++;
}
// write closing folders array to stream
// ]
}
if ([folder.words count] > 1) {
// Write opening words array fragment to stream e.g.
// "Words" : [
// loop through Word member NSManagedObjects here
// (note defensive checks for nulls not included).
NSUInteger count = 0;
for (Word *nestedWord in folder.words)
{
if (count > 0) // print comma to output stream
[nestedFolder acceptVisitor:self];
count++;
}
// write closing Words array to stream
// ]
}
// Print closing Folder object brace to stream (should only be followed
// a comma if there are more members in the folder this object is contained by)
// e.g.
// },
// Note 2b Next object determination code here.
}
- (void)visitWord:(Word*)word
{
// Write to JSON stream
[NSJSONSerialization writeJSONObject:word toStream:_outputStream options: NSJSONWritingPrettyPrinted error:nil];
}
#end
This object is able to "visit" each object in your hierarchy and do some work work with it (in your case write it to a JSON stream). Note you don't need to extract to a dictionary first. You just work directly with the Core Data objects, making them visitable. Core Data contains it's own memory management, with faulting, so you don't have to worry about excessive memory usage.
This is the process. You instantiate the visitor object and then call it's start visiting method passing in the root Folder object of your hierarchy above. In that method, the visitor object "knocks on the door" of the first object to be visited by calling - (void)acceptVisitor:(id<Visitor>)visitor on the object to be visited. The root Folder then "welcomes the visitor in" by calling a method back on the visitor object matching it's own object type, e.g.:
- (void)acceptVisitor:(id<Visitor>)visitor
{
if ([visitor respondsToSelector:#selector(visitFolder:)]) {
[visitor visitFolder:self];
}
}
This in turn calls the visitFolder: method on the visitor object which opens the stream writes the object as JSON and closes the stream. This is the important thing. This pattern may appear complex at first, but I guarantee, if you are working with hierarchies, once you have implemented it you will find it powerful and easy to manage.
To support low memory serial output of a deep hierarchy, I'm suggesting you write your own JSON Folder object to the output stream. Since JSON is so simple, this is much easier than it might at first appear. The alternative is to look for a JSON Library which supports low memory serialised writing of nested objects (I haven't used JSON much so don't know if such exists and is easy to use on iOS). The visitor pattern ensures you need have no more than one NSManagedObject instantiated to work on for each level of the hierarchy (though of course more will inevitably need to be instantiated as you implement hierarchy traversal logic) so this is light on memory usage.
I have given examples of the text string that needs to be written to the output stream. Best practice would dictate using a templating system for this rather than directly writing statically allocated strings. But personally I wouldn't worry about adopting the quick and dirty approach if your deadline is tight.
I've assumed your folder objects contain a folders property providing a set of additional folders. I have also assumed your Folders NSManagedObject class contains a words property containing a set of Words NSManagedObjects. Remember if you stay working in Core Data it will look after ensuring you keep a low memory footprint.
At the end of the visitFolder: method, you can use the following logic.
Check if the Folder's contains any folders and visit each in turn if it does.
If it contains no more folders, check if it contains any Words, and visit each in turn if it does.
Note the above code is the simplest construct for minimising the memory footprint. You may want to optimise it for performance by e.g. only doing an auto-release when a certain batch size is exceeded. However given the problem you have described, it will be best to implement the most memory efficient method first.
If you have polymorphic hierarchies - your on your own :) - get a book out and do some study -managing them is a grad degree in itself.
Clearly this code is untested!
Check the NSFetchRequest documentation. You will see two properties:
- (NSUInteger)fetchOffset;
– fetchBatchSize;
With use of these two properties you can restrict the number of returned NSManagedObjects to a given batch size.
Open a stream you can write too. Set up a loop to execute a fetch request. But set a batch size (x) and then update the fetch offset of the fetch request at the end of the loop code for the next iteration of the loop.
myFetchRequestObject.fetchOffset += x;
Process the batch of data objects writing the JSON data to your open stream before starting the next iteration of the loop.
When either no more objects are returned or the number of objects returned by the fetch are less than the batch size, exit your loop.
Close your stream.
problem was that i had Enable Zombie Objects in the project schema turned on.
For some reason this also carried through to the release build too.
turning it off fixed all my problems.
I ended up also using TheBasicMinds design pattern because its a cool design pattern...

Am I misusing NSString/NSMutableString with my Core Data model?

I'm looping through the results of a fetch request against a Core Data store. For each object in the result list, I am reading several attributes of type string and concatenating them into one string (to be output as a CSV format file).
One particular string of one particular record in my dataset is giving me trouble: extraneous characters (kanji, arabic, etc.) are appended to the end of the string, it does not append properly to my result string, and my CSV file format is hosed.
Here is my code for looping through the fetch results and appending the string:
NSMutableString *reportString = [[NSMutableString stringWithFormat:#"...\n"];
for (int s = 0; s < [frc.sections count]; s++) {
for (int r = 0; r < [[frc.sections objectAtIndex:s] numberOfObjects]; r++) {
NSIndexPath *i = [NSIndexPath indexPathForRow:r inSection:s];
Thread *thread = [frc objectAtIndexPath:i];
NSMutableString *activity = [NSMutableString stringWithString:[thread activity]];
.
.
.
[reportString appendString:activity];
[reportString appendFormat:#",%#\n", client];
}
}
I'm using stringWithString here, but I've also used several other string methods with similar, corrupted, results. One time, Arabic letters appeared. Another time, it was "...random...FSO_CARRIER_ATT#2X.png". I've also tried using a separate fetch results array (instead of a fetched results controller).
One weird thing is that when I do a PO from lldb, the string shows up correctly. This may explain why this "corruption" doesn't show up on my table views, just when I am trying to mash strings together.
My Question
Am I copying the string values from the Core Data model incorrectly and causing this to happen? Is there a technique I am missing out on?
Update:
A screenshot of the watched variable versus an NSLog of the value:
As I mentioned in the comments, this turns out to have been a string encoding/decoding issue.
I formulated a more directed question (How do I properly encode Unicode characters in my NSString?) and got the answer very quickly.
Basically, I was using the proper string manipulation methods, as everyone here agreed. In the end, the encoding parameter I was using to attach the CSV file to the email was incorrect. At the same time, the discrepancy between the variables pane and the lldb pane in Xcode appears to be a legitimate bug. I'll be filing a bug report for that one.
I would put this whole iteration code into one serial background thread. The corruption of the order of your input has certainly to do with the threads competing against each other. You can still use separate threads, but you have to make sure they are executed serially, i.e. that one thread waits for previously scheduled threads to finish.
In Grand Central Dispatch, this would be a thread like
dispatch_queue_t serialThread =
dispatch_queue_create("serialThread", DISPATCH_QUEUE_SERIAL);
You could also use the managed object context with
[context performBlockAndWait:...];

Cannot create PDF document with 400+ pages on iOS

I am using the following pseudocode to generate a PDF document:
CGContextRef context = CGPDFContextCreateWithURL(url, &rect, NULL);
for (int i = 1; i <= N; i++)
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
CGContextBeginPage(context, &mediaBox);
// drawing code
CGContextEndPage(context);
[pool release];
}
CGContextRelease(context);
It works very well with small documents (N < 100 pages), but it uses too much
memory and crashes if the document has more than about 400 pages (it received
two memory warnings before crashing.) I have made sure there were no leaks using
Instruments. What's your advice on creating large PDF documents on iOS? Thanks a lot.
edit: The pdf creation is done in a background thread.
Since you're creating a single document via CGPDFContextCreateWithURL the entire thing has to be held in memory and appended to, something that commonly (though I can't say for certain with iOS and CGPDFContextCreateWithURL) requires a full before and after copy of the document to be kept. No need for a leak to create a problem, even without the before-and-after issue.
If you aren't trying to capture a bunch of existing UIKit-drawn stuff -- and in your sample it seems that you're not -- use the OS's printing methods instead, which offer built-in support for printing to a PDF. UIGraphicsBeginPDFContextToFile writes the pages out to disk as they're added so the whole thing doesn't have to be held in memory at once. You should be able to generate a huge PDF that way.
Probably not the answer you want to hear, but looking at it from another perspective.
Could you consider it as a limitation of the device?... First check the number of pages in the PDF and if it is too large, give a warning to the user. Therefore handling it gracefully.
You could then expand on this....
You could construct small PDF's on the iDevice and if the PDF is too large, construct it server-side the next time the iDevice has a net connection.
If you allocate too much memory, your app will crash. Why is generating an unusually large PDF a goal? What are you actually trying to accomplish?
What about using a memory mapped file to back your CG data consumer? Then it doesn't necessarily have to fit in RAM all at once.
I created an example here: https://gist.github.com/3748250
Use it like this:
NSURL * url = [ NSURL fileURLWithPath:#"pdf.pdf"] ;
MemoryMappedDataConsumer * consumer = [ [ MemoryMappedDataConsumer alloc ] initWithURL:url ] ;
CGDataConsumerRef cgDataConsumer = [ consumer CGDataConsumer ] ;
CGContextRef c = CGPDFContextCreate( cgDataConsumer, NULL, NULL ) ;
CGDataConsumerRelease( cgDataConsumer ) ;
// write your PDF to context `c`
CGPDFContextClose( c ) ;
CGContextRelease( c ) ;
return 0;

Resources