I have a problem with my app, where it consumes a lot of memory and crashes after about 40 minutes. I have run instruments to see if there were any leaks, but none appeared. Inside the app, I am using Mapbox to display maps and drawing a line annotation wherever the user has travelled. I am storing these locations in an array, removing the current annotation and presenting a new one whenever the user has moved.
In my MapViewController.m
- (void)drawCurrentPolyline
{
[self removePolylineForTrip:locationServices.trip forClass:nil];
[self addPolylineForTrip:locationServices.trip forClass:nil];
globalData.locationData = [[NSMutableArray alloc] init];
colour = [globalData convertColourFromString:[globalData.currentTrip objectForKey:#"colour"]];
[colourArray addObject:colour];
if ([currentLocations count] >= 2)
{
for (int i = [currentLocations count] - 2; i <= [currentLocations count] - 1; i++)
{
CLLocation *locationCoordinate = [[CLLocation alloc] initWithLatitude:[[currentLocations[i] objectForKey:#"latitude"] doubleValue] longitude:[[currentLocations[i] objectForKey:#"longitude"] doubleValue]];
[globalData.locationData addObject:locationCoordinate];
}
}
else
{
for (NSDictionary *location in currentLocations)
{
CLLocation *locationCoordinate = [[CLLocation alloc] initWithLatitude:[[location objectForKey:#"latitude"] doubleValue] longitude:[[location objectForKey:#"longitude"] doubleValue]];
[globalData.locationData addObject:locationCoordinate];
}
}
}
- (void)removePolylineForTrip:(int)trip forClass:(MapPreferencesTableViewController *)mapPreferencesTableViewController
{
NSMutableDictionary *dictionary = [tripAnnotions objectForKey:[NSString stringWithFormat:#"trip%d", trip]];
NSMutableArray *annotationArray = [dictionary objectForKey:#"annotions"];
for (RMAnnotation *annotation in annotationArray)
{
if ([[annotation.userInfo objectForKey:#"type"] isEqualToString:#"line"])
{
[mapView removeAnnotation:annotation];
}
}
}
- (void)addPolylineForTrip:(int)trip forClass:(MapPreferencesTableViewController *)mapPreferencesTableViewController
{
globalData.locationData = [[NSMutableArray alloc] init];
globalData.tripData = [globalData.trips objectForKey:[NSString stringWithFormat:#"trip%d", trip]];
NSMutableArray *locationArray = [globalData.tripData objectForKey:#"locationData"];
colour = [globalData convertColourFromString:[globalData.tripData objectForKey:#"colour"]];
[colourArray addObject:colour];
double lineWidth = 5.0;
NSString *type = #"line";
for (NSDictionary *location in locationArray)
{
CLLocation *locationCoordinate = [[CLLocation alloc] initWithLatitude:[[location objectForKey:#"latitude"] doubleValue] longitude:[[location objectForKey:#"longitude"] doubleValue]];
[globalData.locationData addObject:locationCoordinate];
}
if ([locationArray count] > 1)
{
RMAnnotation *annotation = [[RMAnnotation alloc] initWithMapView:mapView
coordinate:((CLLocation *)[globalData.locationData objectAtIndex:0]).coordinate
andTitle:[NSString stringWithFormat:#"Trip %d", trip]];
NSMutableDictionary *annotationData = [[NSMutableDictionary alloc] init];
[annotationData setObject:globalData.locationData forKey:#"locations"];
[annotationData setObject:colour forKey:#"colour"];
[annotationData setObject:[NSString stringWithFormat:#"%f", lineWidth] forKey:#"width"];
[annotationData setObject:type forKey:#"type"];
[annotationData setObject:[NSString stringWithFormat:#"%d", trip] forKey:#"trip"];
annotation.userInfo = annotationData;
// NSLog(#"User Info For Annotation:\n%#", annotation.userInfo);
//annotation.layer = [NSString stringWithFormat:#"%d", trip];
[annotation setBoundingBoxFromLocations:globalData.locationData];
// Add annotation for tracking
[self addAnnotationToTripArray:annotation forTrip:trip];
[mapView addAnnotation:annotation];
}
}
I think the problem might be the fact that I am storing each of the user locations in a mutable array. Hence, I told the app to remove everything in the array once a memory warning has appeared. However, the app still crashes. I cannot seem to find the source of the memory consumption. There are no leaks (except for some small core graphics ones).
Could someone please guide me from where to go from here, or help me find the source of this issue? The simulator used over 1GB of data, and the app became very slow.
Remember, it's not a leak if it's not memory that's been orphaned somehow. Pay attention to your memory usage over time. Instruments will tell you the number of objects living and what kind they are. Things to look out for are things like large arrays of things growing over time and UIView's not getting removed from the superViews.
I had a similar issue and it was because I had MBProgressHUD instances that were hidden, but never removed from their superView.
As #InkGolem states, increasing memory is not necessarily a leak.
Use instruments to check for leaks and memory loss due to retained but not leaked memory. The latter is unused memory that is still pointed to. Use Mark Generation (Heapshot) in the Allocations instrument on Instruments.
For HowTo use Heapshot to find memory creap, see: bbum blog
Basically there method is to run Instruments allocate tool, take a heapshot, run an intuition of your code and another heapshot repeating 3 or 4 times. This will indicate memory that is allocated and not released during the iterations.
To figure out the results disclose to see the individual allocations.
If you need to see where retains, releases and autoreleases occur for an object use instruments:
Run in instruments, in Allocations set "Record reference counts" on on (you have to stop recording to set the option). Cause the picker to run, stop recording, search for there ivar (datePickerView), drill down and you will be able to see where all retains, releases and autoreleases occurred.
Related
I have a iOS app which does alot of calculation and is using standard ARC for memory management. After I run it for a few minutes it crashes due to being out out memory. I checked with Instruments and most of the memory is being eaten up by allocations from a call to NSString's commentsSeparatedByString.
I tried running it in a autorelease pool but that didn't help much. Since there are no references to that string outside of my function, I'm confused why the memory isn't being automatically deallocated. I also have another function which is having the same problem with commentsSeparatedByString.
Here is the code:
- (void) processWorkWithExtraData:(NSData *) extraData
{
#autoreleasepool {
NSString *string = [[NSString alloc] initWithData:extraData encoding:NSUTF8StringEncoding];
NSArray *dataArray = [string componentsSeparatedByString:#","]; // eats up memory like crazy!!!
NSMutableArray *objectArray = [[NSMutableArray alloc] init];
for (int i=0;i<[dataArray count];i += 1)
{
TestObject *p = [[TestObject alloc] initWithFloat:[[dataArray objectAtIndex:i] floatValue]];
[objectArray addObject:p];
}
[self processArray: objectArray]; // just performs math computations on the floats in the objects
}
}
If anyone can let me know why memory would not be freed here please let me know.
Figured out the problem, I thought I was using ARC but I wasn't (:
Good thing is this fixes my memory issues.
Bad thing is that it's much slower (50-70% slower).
I guess that's the price one has to pay for the magic that is ARC.
I'm working on an application for the iPhone and I'm keeping track of the users current location. When the didupdateLocations delegate method actually executes i would like to test if the location in the NSArray is in a predefined array the contains other locations, this array mind you could grow over time.
I'm running a for loop within this method to test against my own array of locations but i would like to move that to a separate thread. So in case my own array with multiple locations grows to a large number the for loop does not freeze my UI.
I have tried it like this but I'm getting undesirable results. I understand that the location tracking definitely happens in a separate thread. However those didupdateLocations execute on a separate thread. The Apple doc's are not very clear on the matter. My end goal again is to compare against my array and not lock the UI.
- (void)locationManager:(CLLocationManager *)manager
didUpdateLocations:(NSArray *)thisLocation {
dispatch_queue_t queue= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// get the last object in the array of locations
CLLocation* location = [thisLocation lastObject];
dispatch_async(queue, ^{
[self checkmyArray:location];
});
}
-(void)checkmyArray:(CLLocation *)workingLocation{
NSLog(#"SoundTheAlarm");
int alarm_on_c = 0;
NSUInteger tmp_count = [theData.LocationsObjectArray count];
BOOL alarm;
NSMutableDictionary * tempObject;
CLLocationDistance distance = 0.0;
for (int i = 0; i < tmp_count; i++) {
tempObject= [theData.LocationsObjectArray objectAtIndex:i];
thisLoc = [[tempObject objectForKey:#"onoff"] isEqual:#YES];
if (thisLoc) {
//check if we are near that location
double lat = [[tempObject objectForKey:#"latitude"] doubleValue];
double lon = [[tempObject objectForKey:#"longitude"] doubleValue];
// goal = [[CLLocation alloc] initWithLatitude:40.097771 longitude:-74.941399];
goal = [[CLLocation alloc] initWithLatitude:lat longitude:lon];
// check the destination between current location and goal location - in meters
distance = [goal distanceFromLocation:workingLocation];
NSLog(#"distance %f\n", distance);
}
// if distance from goal is less than 350 meters
if (distance <= 350){
[self scheduleNotification:[tempObject objectForKey:#"name"]];
// turn off tracking for this location
[tempObject setObject:#NO forKey:#"onoff"];
[theData.LocationsObjectArray replaceObjectAtIndex:i withObject:tempObject];
NSIndexPath *path = [NSIndexPath indexPathForRow:i inSection:0];
ExtendedSavedCellTableViewCell *cell = (ExtendedSavedCellTableViewCell *)[self.tableView cellForRowAtIndexPath:path];
cell.switchView.on = NO;
// save the update to the switch to the database as well
NSString *lat = [tempObject objectForKey:#"latitude"];
/*check to determine if the uiswitch is turned off or on.*/
[self fetchedResultsController:#NO lat:lat index:path];
[self displayAlertViewForAlarm:[tempObject objectForKey:#"name"]];
}
-(void)displayAlertViewForAlarm:(NSString *)nameOfLocation{
dispatch_async(dispatch_get_main_queue(), ^(void) {
UIAlertView *myAlert = [[UIAlertView alloc] initWithTitle:#"Destination reached"
message:nameOfLocation
delegate:self
cancelButtonTitle:#"Go Away"
otherButtonTitles:#"Notify", nil];
[myAlert show];
});
}
It's generally a bad idea to use threads in iOS if you can at all avoid it. In your case, I'd implement the function which does the looping to automatically pop out of the loop after too many iterations and then schedule the next chunk of iterating to happen in another pass through the event loop. In other words, something like this:
- (void) checkLocationsStartingAt:(NSNumber)start
{
NSInteger s = (start) ? [start intValue] : 0;
for (i = s; i < list.length; i++) {
if (i > TOO_MANY) {
[self performSelector:#selector(checkLocationsStartingAt:)
withObject:#(i)
afterDelay:0.001];
return;
} else {
// check element at i
}
}
}
see: NSObject Reference
You are checking one location against an array of other locations.
You display an alert for time you are closer than 350m from a location.
You could be within 350m of many items in the list.
You have no code prevent multiple alerts.
So you will sometimes have many alerts.
You have a couple options. One will probably suit your needs better than the others. Possibly one I didn't list.
You could re-use a single UIAlertView instance and check the
visible property on it. If it's already visible, do nothing.
You could break out of the loop once you get a single "hit" on being
<350m.
You could stick in a BOOL which records if you've shown the alert and
check the BOOL every time before you show the alert again.
This is a question related to Are there APIs for custom vibrations in iOS?.
I am able to create custom vibration patterns, but have no control over the intensity.
This is copied over from Kevin Cao's answer that enables custom vibration patterns:
NSMutableDictionary* dict = [NSMutableDictionary dictionary];
NSMutableArray* arr = [NSMutableArray array ];
[arr addObject:[NSNumber numberWithBool:YES]]; //vibrate for 2000ms
[arr addObject:[NSNumber numberWithInt:2000]];
[arr addObject:[NSNumber numberWithBool:NO]]; //stop for 1000ms
[arr addObject:[NSNumber numberWithInt:1000]];
[arr addObject:[NSNumber numberWithBool:YES]]; //vibrate for 1000ms
[arr addObject:[NSNumber numberWithInt:1000]];
[arr addObject:[NSNumber numberWithBool:NO]]; //stop for 500ms
[arr addObject:[NSNumber numberWithInt:500]];
[dict setObject:arr forKey:#"VibePattern"];
[dict setObject:[NSNumber numberWithInt:1] forKey:#"Intensity"];
AudioServicesPlaySystemSoundWithVibration(4095,nil,dict);
The line of code that adds the key #"Intensity" with an int value doesn't do the trick and I don't know how to look inside the AudioServicesPlaySystemSoundWithVibration method to figure it out. What do I have to pass to it so that it actually changes the intensity?
Right now, it doesn't matter if I pass 1, 1000, 0.4 or 0.0001, it's always the same intensity (on an iPhone 4 with iOS7). Can anyone recreate this?
I would like to be able not only to create vibration patterns, but a smooth vibration envelope. How to?
(As this is a research project for instrument design, I am not (yet) concerned with the App store restrictions.)
Change the numberWithInt call into numberWithFloat, and change the intensity so it's between 0 and 1. I thought it was weird when they used an int rather than a float.
Edit: Here's a copy/paste that should work for your code to invoke custom vibration:
#pragma mark - Custom vibration methods
-(void)invokeCustomVibrationWithStartStopTimes:(NSArray*)startStopTimes andIntensity:(float)intensity {
BOOL startOrStop = YES;
NSMutableArray* arr = [#[] mutableCopy];
double time = 0;
for (NSNumber *x in stopStartTimes) {
[arr addObject:x]
startOrStop = !startOrStop;
[arr addObject:#(startOrStop)];
time = [x doubleValue] / 1000.0;
}
AudioServicesPlaySystemSoundWithVibration(4095,nil,{#"VibePattern":arr,#"Intensity":#(intensity)})
[self performSelector:#selector(stop) withObject:nil afterDelay:time];
}
-(void)stop {
AudioServicesStopSystemSound(4095); // stop buzzing the phone
}
For startStopTimes, it should alternate between times started and times stopped. Passing in this array:
#[#(2000), #(1000), #(1000), #(500)]
Will do what the example code did. In this case, it will start for 2000 ms, stop for 1000 ms, start for 1000 ms, and stop for 500 ms.
stop is called to stop the sound. The way I have it set up, it stops sounds after the total amount of time sent in.
You may have noticed I've been using array/number literals rather than using [NSArray arrayWithObjects: ... , nil]; or [NSNumber numberWith...];. This makes your code a lot shorter. Also, I marked the beginning with a #pragma mark. Use that to organize it better. Hope it helps!
I'm developing an app in iOS
this application captures signatures and also redraws them back from a database...
The problem appeared when I added threads, for printing on the labels the biometric data on real time while the signature is being drawn, randomly it crashes, I mean sometimes it draws the signature complete, sometimes just 3/4 of the signature or not even a half of it....
Used a tool called Instruments trying to detect the NSZombies and this is what I got...
and looking in the code, this is the portion of code I found...
this is the piece of code where it crashes:
-(void)imprimeValoresFirmaEnTabla:(NSMutableArray *)valores{
if ([valores count] == 3) {
if ([valores objectAtIndex:0] != nil) self.pressureStrokeLabel.text = [valores objectAtIndex:0];
if ([valores objectAtIndex:1] != nil) self.speedStrokeLabel.text = [valores objectAtIndex:1];
if ([valores objectAtIndex:2] != nil) self.locationStrokeLabel.text = [valores objectAtIndex:2];
}
}
it says that -[NSConcreteMutableAtributedString lenght] is the responsible of the Zombie
this is my code:
- (IBAction)drawSignature{
[self clear]; // clear the canvas
NSData *datos = [self desSerializarFirma:[self traerFirmadeBD]]; //unpack the registered signature and store it on the NSData object
if (datos.length <= 1) // if there's no data stored on database
[[[UIAlertView alloc]initWithTitle:#"Notification" message:#"For showing your signature, it is required to register it first, draw your signature on the canvas and tap the Save button" delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil] show];
if (datos.length > 1) { // if is data stored on database
//store the signature on the object
self.firmaCompleta = [NSKeyedUnarchiver unarchiveObjectWithData:datos];
//this loop draws each point on the canvas
for (int i = 0; i < [self.firmaCompleta.location count]; i++) {
SmoothStroke *currentStroke = [canvasView getStrokeForTouchHash:[[self.firmaCompleta.touchHash objectAtIndex:i] intValue]];
//this draws each point using the biometric info
[canvasView addLineToAndRenderStroke:currentStroke
toPoint:CGPointFromString([self.firmaCompleta.location objectAtIndex:i])
toWidth:[canvasView widthForPressure:[[self.firmaCompleta.pressure objectAtIndex:i] intValue]]
toColor:[canvasView colorForPressure:[[self.firmaCompleta.pressure objectAtIndex:i] intValue]]];
//this stores the biometric data in an array for sending to background thread
if (!valoresFirma) valoresFirma = [[NSMutableArray alloc]init];
[valoresFirma removeAllObjects];
[valoresFirma addObject:[NSString stringWithFormat: #"%f", [canvasView widthForPressure:[[self.firmaCompleta.pressure objectAtIndex:i] intValue]]]];
[valoresFirma addObject:[NSString stringWithFormat:#"%f",velocidadTrazo]];
[valoresFirma addObject:[self.firmaCompleta.location objectAtIndex:i]];
//this triggers the tread for printing the values on labels (real time)
[NSThread detachNewThreadSelector:#selector(imprimeValoresFirmaEnTabla:) toTarget:self withObject:valoresFirma];
}
}
}
and the code called by the thread:
-(void)imprimeValoresFirmaEnTabla:(NSMutableArray *)valores{
if ([valores count] == 3) {
if ([valores objectAtIndex:0] != nil) self.pressureStrokeLabel.text = [valores objectAtIndex:0];
if ([valores objectAtIndex:1] != nil) self.speedStrokeLabel.text = [valores objectAtIndex:1];
if ([valores objectAtIndex:2] != nil) self.locationStrokeLabel.text = [valores objectAtIndex:2];
}
}
thanks in advance for your support
Don't do threading like that, it's too easy to shoot yourself in the foot. Create an NSOperation subclass to do the drawing. Make a custom initializer that takes an array. Store the array with the operation object so you know it won't be released on you. It's a few extra lines of code compared to detachNewThreadSelector but it's much safer. Plus you can cancel the operation if the user navigates away from the view that you're showing.
Read up on operations and operation queues here:
http://developer.apple.com/library/mac/#documentation/General/Conceptual/ConcurrencyProgrammingGuide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40008091
In an app that has many different UITableViews, I have found myself frequently using temporary arrays to import the data used to populate the table view, determine number of rows, sections, headers, footers, etc. I'm wondering if, because these array need to be created for each cell in the table over and over if declaring the static so they don't need to be created again will help performance, because right now these arrays are being created in cellForRowAtIndexPath:, numberOfRowsInSections:, numberOfSectionsInTableView:,footerForSection:`. Would declaring this many static arrays (which might contain a decent amount of information, say a couple thousand doubles and a couple hundred strings), in the long run, help or hurt me? I know that a static array stays in memory for the course of the application's life, so will this many static arrays be detrimental? Assuming that this process occurs in 4-5 view controllers across the course of the app, we are talking about 15-20 copies of this array just sitting around. What is my best option here? Thanks
Edit: I am using a singleton which holds the values. The real reason for the temporary arrays is to keep the code clean. I can do something like
dataArray = [[SingletonDataController sharedSingleton] dataArray]
objectAtIndex:CURRENTLY_SELECTED_DATA_INDEX;
then
myTitleString = [dataArray objectAtIndex:keyTitleStringIndexKey];
instead of grouping it all into one unreadable statement like:
myTitleString = [[[[SingletonDataController sharedSingleton] dataArray]
objectAtIndex:CURRENTLY_SELECTED_INDEX] objectAtIndex:keyTitleStringIndexKey];
I have performed some tests of my own, comparing the time it takes to create the table view with/without static initialization. These are the results:
2012-01-29 18:31:57.539 XXXXXXX[711:707] static average: 0.058798
2012-01-29 18:31:57.543 XXXXXXX[711:707] nonstatic average: 0.058395
As you can see, the static initialization is actually slower than the non-static, but only by a few ten-thousandths of a second. This is probably just a product of inaccurate measurement, but the results say enough to convince me that the difference is small enough to dismiss. Mystery solved.
When you do the above you are not actually creating a new array, just grabbing a pointer to that array. You are not copying the actual data.
By keeping your code clean you are losing only the performance of creating memory for a pointer and assigning a pointer a value. So no, you are not losing performance.
The idea of keeping your code clean is much more important than this marginal difference in an extra pointer here and there.
Edit:
I did some testing between the two and as expected, both options perform very similar.
NSMutableArray *data1 = [[NSMutableArray alloc] init];
NSMutableArray *data2 = [[NSMutableArray alloc] init];
NSArray *all = [[NSArray alloc] initWithObjects:data1,data2,nil];
for(int i=0;i<1000;i++)
{
[data1 addObject:[[NSNumber alloc] initWithInt:arc4random()]];
[data2 addObject:[[NSNumber alloc] initWithInt:arc4random()]];
}
double startTime = CACurrentMediaTime();
for(int i=0;i<1000;i++)
{
NSArray *get1 = [all objectAtIndex:0];
NSArray *get2 = [all objectAtIndex:1];
//int get1Index = arc4random() % [get1 count];
//int get2Index = arc4random() % [get2 count];
//NSLog(#"Object at %d: %f", get1Index, [[get1 objectAtIndex:get1Index] doubleValue]);
//NSLog(#"Object at %d: %f", get2Index, [[get2 objectAtIndex:get2Index] doubleValue]);
NSLog(#"Object at %d: %f", i, [[get1 objectAtIndex:i] doubleValue]);
NSLog(#"Object at %d: %f", i, [[get2 objectAtIndex:i] doubleValue]);
}
NSLog(#"Time with temp array:%f", CACurrentMediaTime() - startTime);
startTime = CACurrentMediaTime();
for(int i=0;i<1000;i++)
{
//int get1Index = arc4random() % [[all objectAtIndex:0] count];
//int get2Index = arc4random() % [[all objectAtIndex:1] count];
//NSLog(#"Object at %d: %f", get1Index, [[[all objectAtIndex:0] objectAtIndex:get1Index] doubleValue]);
//NSLog(#"Object at %d: %f", get2Index, [[[all objectAtIndex:1] objectAtIndex:get2Index] doubleValue]);
NSLog(#"Object at %d: %f", i, [[[all objectAtIndex:0] objectAtIndex:i] doubleValue]);
NSLog(#"Object at %d: %f", i, [[[all objectAtIndex:1] objectAtIndex:i] doubleValue]);
}
NSLog(#"Time without temp array:%f", CACurrentMediaTime() - startTime);
//With random access
//2012-01-28 13:44:12.721 test[23164:f803] Time with temp array:0.924193
//2012-01-28 13:44:13.641 test[23164:f803] Time without temp array:0.919250
//2012-01-28 13:44:44.892 test[23191:f803] Time with temp array:0.926337
//2012-01-28 13:44:45.812 test[23191:f803] Time without temp array:0.920447
//With incremental access
//2012-01-28 13:46:43.948 test[23231:f803] Time with temp array:0.935009
//2012-01-28 13:46:44.927 test[23231:f803] Time without temp array:0.978455
//2012-01-28 13:47:40.317 test[23254:f803] Time with temp array:1.173752
//2012-01-28 13:47:41.307 test[23254:f803] Time without temp array:0.989263
The commented out sections are the sections I used for testing random access, for the incremental access I used the current code. Without temp arrays is a fractions quicker, but not noticeably. Not enough to sacrifice readability. I guess that is just the process of writing it out to a variable that slows it down, but, at the same time, having a temp array that is not embedded is much quicker. If you were using the embedded array many times, you would have to do 2 memory accesses instead of 1. So if you are going to use the embedded array several times I imagine the gain would significantly compensate for the loss of using a temp array.