Weird leaks in UIPickerView methods - ios

Building a custom UIPickerView for so I can let my users select time as 24 hours without having to go into the Settings app and turn on 24 hour time for the entire phone. Got some stubborn leaks of a couple strings and an array, and I could really use some help.
There are only three places where the arrays I'm creating with the strings are used. hours and minutes are both NSArray synthesized properties as well as ivars.
a) In viewWillAppear:animated, where the strings and arrays are actually created:
if (TwentyFourHourMode) {
//set up arrays for 24 hour picker
NSMutableArray *hoursMutable = [[NSMutableArray alloc] init];
NSString *hourString;
for (int i = 0; i < 24; i++) {
if (i < 10) {
hourString = [NSString stringWithFormat:#"0%i", i];
} else {
hourString = [NSString stringWithFormat:#"%i", i];
}
[hoursMutable addObject:hourString];
}
self.hours = [[NSArray alloc] initWithArray:hoursMutable];
[hoursMutable release];
NSMutableArray *minutesMutable = [[NSMutableArray alloc] init];
NSString *minuteString;
for (int i = 0; i < 60; i++) {
if (i < 10) {
minuteString = [NSString stringWithFormat:#"0%i", i];
} else {
minuteString= [NSString stringWithFormat:#"%i", i];
}
[minutesMutable addObject:minuteString];
}
self.minutes = [[NSArray alloc] initWithArray:minutesMutable];
[minutesMutable release];
//more stuff which does not leak or reference the arrays/strings in question
} else {
//unrelated crap
}
b) in my UIPickerView delegate methods - everything that uses these two arrays:
-(NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView {
return 2;
}
-(NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component {
if (component == 0) {
return self.hours.count;
} else if (component == 1) {
return self.minutes.count;
} else {
return 0;
}
}
- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component
{
if (component == 0) {
return [self.hours objectAtIndex:row];
} else if (component == 1) {
return [self.minutes objectAtIndex:row];
} else {
return nil;
}
}
- (CGFloat)pickerView:(UIPickerView *)pickerView widthForComponent:(NSInteger)component {
switch(component) {
case 0: return 44;
case 1: return 50;
default: return 44;
}
}
- (void) pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component
{
if (component == 0) {
[hour24 release];
hour24 = [self.hours objectAtIndex:row];
[hour24 retain];
} else if (component == 1) {
[minute24 release];
minute24 = [self.minutes objectAtIndex:row];
[minute24 retain];
}
c) and last but not least, in dealloc:
//set arrays to nil
self.hours = nil;
self.minutes = nil;
//release arrays
[hours release];
[minutes release];
Analyze is coming up clean, but Instruments is telling me hourString, minuteString, and self.hours are all being leaked. What really drives me nuts is that self.minutes isn't being leaked and it appears to be the same format of code as self.hours - I even copy-pasted and still get the same leak/no leak combo.
I'll be damned if I can figure out where this is coming from. Any ideas? Any further code y'all might need to help? Thanks, guys!
Edit: EmptyStack's suggestion stopped self.hours and minuteString from being leaked, but hourString is still leaking and there's now a new leak in this code just below the stuff above in viewWillAppear:animated (self.incomingTime is a synthesized NSString property, all arrays here are initialized locally):
NSArray *splitStrings = [self.incomingTime componentsSeparatedByString:#":"];
NSString *hourToDisplay = [splitStrings objectAtIndex:0];
//set this here so it doesn't give a null value
hour24 = [[NSString alloc] initWithString:hourToDisplay];
NSString *minuteSplitter = [splitStrings objectAtIndex:1];
NSArray *splitMinutes = [minuteSplitter componentsSeparatedByString:#" "];
NSString *minuteToDisplay = [splitMinutes objectAtIndex:0];
minute24 = [[NSString alloc] initWithString:minuteToDisplay];
Edit 2: Oh, for crying out loud, now minuteString is leaking again. I'm going to bed before my head explodes. Any suggestions overnight would be most welcome.

The problems are in the following lines,
self.hours = [[NSArray alloc] initWithArray:hoursMutable];
self.minutes = [[NSArray alloc] initWithArray:minutesMutable];
It seems hours and minutes are "retained properties" and you are allocating objects while assigning it to the properties. For example on the first line, [NSArray alloc] increases the retainCount by 1 and the setter self.hours in turn increases the retainCount by 1. Finally the retainCount becomes 2, which causes the leak even after you release those objects. You can use convenience constructors in these cases.
self.hours = [NSArray arrayWithArray:hoursMutable];
self.minutes = [NSArray arrayWithArray:minutesMutable];
And even a simpler way is to directly assign those arrays like this,
self.hours = hoursMutable;
self.minutes = minutesMutable;

Okay, finally found it, for anyone stumbling across this in the future: hour24 and minute24 were not being properly released in dealloc, so it was leaking strings and arrays all over the place. I found it by commenting out the code posted in the 2nd edit and putting it back in line by line until the leak sprang up. Thanks for the suggestions!

Related

iOS: Tracking amount of allocated objects

We can use instruments for various kinds of analysis. But many programmers find this tool to be too complicated and too heavy to bring real value.
Is there a simple way to track all objects of a specific class, and for each to know who exactly was allocating them and to verify that they are being freed correctly?
The answer is yes! there is a way, and I'll demo it in my answer below
Tracking allocations easily:
How to use: you can put the 150 lines of code below into a file named AllocTracker.m and drag it into your project files.
Use the check box at the right pane of Xcode to enable/disable it in your compilation target.
What you'll get?
when enabled, this module will track all allocations and deallocations of UIImage objects and log them. (It can easily be modified for tracking other classes.)
In addition to logging every allocation and deallocation, it will periodically (currently every 15 seconds) dump all objects which are currently allocated, with some added info and the call stack which allocated them.
What is the added value?
This code was used in big projects to get rid of orphan objects which were left allocated without notice, allowing to significantly reduce the memory footprint of the app and fix memory leaks.
So here is the code for AllocTracker.m:
#define TRACK_ALLOCATIONS
#ifdef TRACK_ALLOCATIONS
#import <UIKit/UIKit.h>
#define TIMER_INTERVAL 15
#implementation UIApplication(utils)
+(NSString *)dateToTimestamp:(NSDate *)date
{
if (date == nil) {
date = [NSDate date];
}
static NSDateFormatter *dateFormatter = nil;
if (!dateFormatter) {
dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:#"en_US_POSIX"];
[dateFormatter setDateFormat:#"HH:mm:ss.S"];
}
NSString *ts = [dateFormatter stringFromDate:date];
return ts;
}
+(NSString*) getCaller:(int)stackDepth
{
#ifndef DEBUG
return #"NON DBG";
#else
NSArray *symbols = [NSThread callStackSymbols];
int lastIndex = (int)(symbols.count - 1);
if (lastIndex < 3) {
return #"NO DATA";
}
NSMutableString *result = [NSMutableString string];
int foundCount = 0;
for (int ix=3; ix <= lastIndex; ix++) {
NSString *line = symbols[ix];
NSRange rng1 = [line rangeOfString:#"["];
if (rng1.location == NSNotFound) {
continue;
}
NSRange rng2 = [line rangeOfString:#"]"];
NSString *caller = [line substringWithRange:NSMakeRange(rng1.location+1, rng2.location-rng1.location-1)];
if (foundCount > 0) { //not first
[result appendString:#"<--"];
}
[result appendString:caller];
if (++foundCount == stackDepth) {
break;
}
}
return (foundCount > 0) ? result : #"NO SYMBOL";
#endif
}
#end
#implementation UIImage(memoryTrack)
static NSMapTable *g_allocsMap;
static NSTimer *g_tmr;
static NSDate *g_lastDump = nil;
+(void)gotTimer:(NSTimer *)timer
{
[self dumpAllocs];
}
+(void)startTimer
{
static int count = 0;
g_tmr = [NSTimer scheduledTimerWithTimeInterval:15 target:self selector:#selector(gotTimer:) userInfo:#(count++) repeats:YES];
NSLog(#"starting timer %i", count);
}
+(void)cancelTimer
{
[g_tmr invalidate];
g_tmr = nil;
}
+(void)dumpAllocs
{
dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), ^{
NSMutableString *str = [NSMutableString string];
[str appendString:#"\n#$# ========== Non-freed UIImages =========\n"];
NSMutableArray *sorted = [NSMutableArray array];
//make sure map is not changed while enumerating
static int s_ts_start = -1;
#synchronized (g_allocsMap) {
NSEnumerator *keysEnum = [g_allocsMap keyEnumerator];
UIImage *img;
while (img = [keysEnum nextObject]) {
NSString *value = [g_allocsMap objectForKey:img];
if (value) { //might be nulled because declared as weak
NSUInteger memUsed = CGImageGetHeight(img.CGImage) * CGImageGetBytesPerRow(img.CGImage);
NSString *objData = [NSString stringWithFormat:#"mem=%5ikb, size=%4ix%-4i", (int)(memUsed/1024), (int)img.size.width, (int)img.size.height];
NSString *line = [NSString stringWithFormat:#"%p - %# [%#]\n", img, objData, value];
if (s_ts_start<0) {
s_ts_start = (int)[line rangeOfString:#"["].location + 1;
}
if (line.length > (s_ts_start+10)) {
[sorted addObject:line];
}
}
}
}
if (sorted.count > 0) {
[sorted sortUsingComparator: ^NSComparisonResult(NSString *s1, NSString *s2)
{
//we expect '0x15a973700 - mem=3600kb, size=640x360 [16:14:27.5: UIIma...'
NSString *ts1 = [s1 substringWithRange:NSMakeRange(s_ts_start, 10)];
NSString *ts2 = [s2 substringWithRange:NSMakeRange(s_ts_start, 10)];
return [ts1 compare:ts2];
}];
int ix = 0;
for (NSString *line in sorted) {
[str appendFormat:#"#$# %3i) %#", ix++, line];
}
}
[str appendString:#"#$# ======================================================\n"];
NSLog(#"%#", str);
});
}
+(instancetype)alloc
{
NSString *caller = [UIApplication getCaller:4];
#synchronized (self) {
id obj = [super alloc];
NSLog(#"#$# UIImage alloc: [%p], caller=[%#]", obj, caller);
NSDate *now = [NSDate date];
NSString *value = [NSString stringWithFormat:#"%#: %#", [UIApplication dateToTimestamp:now], caller];
if (!g_allocsMap) {
g_allocsMap = [NSMapTable mapTableWithKeyOptions:NSMapTableWeakMemory valueOptions:NSMapTableStrongMemory];
}
[g_allocsMap setObject:value forKey:obj];
if (!g_lastDump) {
[self startTimer];
g_lastDump = now;
}
return obj;
}
}
-(void)dealloc
{
NSLog(#"#$# UIImage dealloc: [%#]", self);
}
#end
#endif //TRACK_ALLOCATIONS
How it works?
We create a category of UIImage and set our own version for alloc and dealloc. Every allocated object is saved into an NSMapTable object which works like a dictionary but allow storing object with weak pointers.
For convenience we were adding two methods under UIApplication which can be used by other modules if an appropriate header file is created. One method is for formatting the timestamp, and the other is for reading the call stack (only works in debug builds).
Tip for use:
if you use a real device and install idevicesyslog (brew install libimobiledevice), you can use the terminal to see all allocation debug, like this:
idevicesyslog | grep "#\$#"

Having troubles with UIPickerView

So I am getting a bizarre error when using UIPickerView. Before jumping into the code, a basic explanation of the app is in order:
This app uses a UISegmentedControl to select from a few different possibilities. Based on the selection from the segmented control, the UIPickerView is populated with different arrays. The picker view is guaranteed to have two (no more, no less) components. The arrays are of different sizes, this is where I run into the snag. I have some tests set up in the code to keep the values within the range of the arrays, but something is not working right.
Now onto the code:
-(void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component
{
NSMutableArray *array;
int x = self.segmentController.selectedSegmentIndex;
if (x == 0) {
array = [NSMutableArray arrayWithArray:self.length];
}
else if (x == 1) {
array = [NSMutableArray arrayWithArray:self.time];
}
else if (x == 2) {
array = [NSMutableArray arrayWithArray:self.speed];
}
//component == 0 has no problems whatsoever
if (component == 0) {
if (row < [array count]){
self.label1.text = [array objectAtIndex:row];
self.component0 = row;
}
else {
self.label1.text = [array objectAtIndex:[array count] - 1];
self.component0 = (NSInteger)([array count] - 1);
}
}
//component == 1 has problems, it always thinks that the row number is less than the array count
else if (component == 1) {
if (row < [array count]){
self.label2.text = [array objectAtIndex:row];
self.secondComponent = row;
}
else {
self.label2.text = [array objectAtIndex:[array count] - 1];
self.secondComponent = (NSInteger)([array count] - 1);
}
}
else {
NSLog(#"ERROR: Components out of range");
}
//If I comment out this part, the program works as expected
if (x == 0) {
[self convertLength];
}
else if (x == 1) {
[self convertTime];
}
else if (x == 2) {
[self convertSpeed];
}
}
The error comes when trying to read a value from an array, this is the error:
* Terminating app due to uncaught exception 'NSRangeException', reason: '* -[__NSArrayM objectAtIndex:]: index 8 beyond bounds [0 .. 7]'
I get this error when I go the the second segment (time) and scroll all the way to the bottom on the second component, and then go to the first index.
If I try to do the same thing with the first component, it works exactly the way it is supposed to, hence I am very confused, as I use basically the exact same code to control the two components.
This is how I load/reload the picker view:
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)thePickerView {
return 2;
}
- (NSInteger)pickerView:(UIPickerView *)thePickerView numberOfRowsInComponent:(NSInteger)component {
int x = 0;
if (self.segmentController.selectedSegmentIndex == 0) {
x = [self.length count];
}
else if (self.segmentController.selectedSegmentIndex == 1)
{
x = [self.time count];
}
else if (self.segmentController.selectedSegmentIndex == 2)
{
x = [self.speed count];
}
return x;
}
- (NSString *)pickerView:(UIPickerView *)thePickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component {
NSString *string;
NSArray *array = [[NSArray alloc] init];
if (self.segmentController.selectedSegmentIndex == 0) {
array = [NSArray arrayWithArray:self.length];
}
else if (self.segmentController.selectedSegmentIndex == 1)
{
array = [NSArray arrayWithArray:self.time];
}
else if (self.segmentController.selectedSegmentIndex == 2)
{
array = [NSArray arrayWithArray:self.speed];
}
if (row >= [array count]) {
string = [array objectAtIndex:[array count] - 1];
NSLog(#"Out of Range");
}
else {
string = [array objectAtIndex:row];
}
return string;
}
- (IBAction)segmentDidChange:(id)sender {
[self.pickerView reloadAllComponents];
[self pickerView:self.pickerView didSelectRow:self.component0 inComponent:0];
[self pickerView:self.pickerView didSelectRow:self.secondComponent inComponent:1];
}
Other useful information:
length array count: 8
time array count: 9
speed array count: 8
When I log the variables, they behave the way they are expected to, they change their value to be within the range of the "controller array" before I try to access any data within said array, but for whatever reason the second component wants to access an index not contained within the array.

Reading Value from UIDatePicker UIDatePickerModeCountDownTimer

Trying to get the amount of time selected by a user from a UIDatePicker set to mode:"UIDatePickerModeCountDownTimer" in InterFace Builder. Some other stacks say to just check the value of the property countDownDuration on the object.
#property (weak, nonatomic) IBOutlet UIDatePicker *hereIsThePicker;
...
NSLog(#"%f", self.hereIsThePicker.countDownDuration);
But this value often reports a range of values, I've seen numbers such as 70, 80, 113, etc for a selection of "0 Hours, 0 Minutes". So apparently countDownDuration is not what I need.
Does anyone have any hints or clues on how to use UIDatePickerModeCountDownTimer and convert the selection to number of seconds selected? so 1 Minute Selected = 60 Seconds, or 1 Hour 5 Minutes Selected = 3900 seconds ?
Been trying to track this down for 2 days now and just can't seem to get my head around what is going on.
Here is a screenshot of an example application that I setup that just has these two components. You can see the IBOutlet, and a button with an IBAction that is fetching the time and NSLogging it to the bottom of the screen:
Finally after trying random stuff for days, the simple answer, for whatever reason is you need to set a value to countDownDuration, even if you don't need to:
[self.timerPicker setCountDownDuration:60.0f]; //Defaults to 1 minute
In your header .h file put this
#interface ViewController : UIViewController
{
__weak IBOutlet UIPickerView *datePick;
NSMutableArray *hoursArray;
NSMutableArray *minsArray;
NSTimeInterval interval;
}
- (IBAction)calculateTimeFromPicker:(id)sender;
in your .m file put this
- (void)viewDidLoad
{
[super viewDidLoad];
//initialize arrays
hoursArray = [[NSMutableArray alloc] init];
minsArray = [[NSMutableArray alloc] init];
NSString *strVal = [[NSString alloc] init];
for(int i=0; i<61; i++)
{
strVal = [NSString stringWithFormat:#"%d", i];
//NSLog(#"strVal: %#", strVal);
//Create array with 0-12 hours
if (i < 13)
{
[hoursArray addObject:strVal];
}
//create arrays with 0-60 mins
[minsArray addObject:strVal];
}
NSLog(#"[hoursArray count]: %d", [hoursArray count]);
NSLog(#"[minsArray count]: %d", [minsArray count]);
}
//Method to define how many columns/dials to show
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView
{
return 2;
}
// Method to define the numberOfRows in a component using the array.
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent :(NSInteger)component
{
if (component==0)
{
return [hoursArray count];
}
else
{
return [minsArray count];
}
}
// Method to show the title of row for a component.
- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component
{
switch (component)
{
case 0:
return [hoursArray objectAtIndex:row];
break;
case 1:
return [minsArray objectAtIndex:row];
break;
}
return nil;
}
- (IBAction)calculateTimeFromPicker:(id)sender
{
NSString *hoursStr = [NSString stringWithFormat:#"%#",[hoursArray objectAtIndex:[pickerView selectedRowInComponent:0]]];
NSString *minsStr = [NSString stringWithFormat:#"%#",[minsArray objectAtIndex:[pickerView selectedRowInComponent:1]]];
int hoursInt = [hoursStr intValue];
int minsInt = [minsStr intValue];
interval = 0 + (minsInt*60) + (hoursInt*3600);
NSString *totalTimeStr = [NSString stringWithFormat:#"%f",interval];
}

Change index of specific char of NSString

I have a string, for example "Soccer". Now I want to move every "e" by lets say 2 indexes(right word?), so my string looks like this = "erSocc". This has to work with whitespace and negative/- indexes.
I came a cross with this, not perfect working, solution:
NSString* text = #"Soccer";
NSString* sign = #"c";
int index = 1;
NSMutableArray* arrayText = [[NSMutableArray alloc]init];
NSMutableArray* arraySignNewPosition = [[NSMutableArray alloc]init];
NSMutableArray* arrayOldSignPosition = [[NSMutableArray alloc]init];
for(int i=0;i<(text.length);i++)
{
[arrayText addObject:[text substringWithRange:NSMakeRange(i, 1)]];
if ([[arrayText objectAtIndex:i]isEqualToString:sign])
{
[arrayOldSignPosition addObject:[NSNumber numberWithInt:i]];
if ((i+index)>(text.length-1))
{
int indexDifference = (i+index)-(text.length);
[arraySignNewPosition addObject:[NSNumber numberWithInt:indexDifference]];
}
else
{
[arraySignNewPosition addObject:[NSNumber numberWithInt:(i+index)]];
}
}
}
for (NSNumber* number in arraySignNewPosition)
{
[arrayText insertObject:sign atIndex:number.integerValue];
if(number.integerValue-index>0)
{
[arrayText removeObjectAtIndex:(number.integerValue-index)];
}
else
{
[arrayText removeObjectAtIndex:((arrayText.count-1)+(number.integerValue-index))];
}
}
I know the code is not working perfectly, but I would like to know if this is the right way or if there are some Cocoa functions I could use to accomplish my goal. Thanks for your time.
You're really just getting substrings and moving them around, so you could do something like this:
- (NSString *)shiftRight:(NSUInteger)places
{
NSAssert(places > 0, #"places must be greater than 0");
NSAssert(places < [self length], #"places must be less than the length of the string");
places = [self length] - places;
NSString *start = [self substringFromIndex:places];
NSString *end = [self substringToIndex:places];
return [start stringByAppendingString:end];
}
Here's a complete code listing, with examples.

better way of removing double (NSManagedObjects) from NSArray (also logic flaw)

I made the following method to remove doubles, however it doesn't fully work. Any suggestions?
Thank you for the help,
-(NSMutableArray*)removeDuplicateCars:(NSMutableArray*)array{
NSMutableArray *noDuplicates =[[NSMutableArray alloc]init];
for (int i=0; i<[array count]; i++) {
int counter =0;
Car *car =(Car*)[array objectAtIndex:i];
if ([noDuplicates count]==0) {
[noDuplicates addObject:car];
}
for (int i=0; i<[noDuplicates count]; i++) {
Car *car2 =(Car*)[array objectAtIndex:i];
if (![car.name isEqualToString:car2.name]) {
counter++;
}
}
if (counter==[noDuplicates count]) {
[noDuplicates addObject:car];
}
}
NSLog(#"number of results = %i",[noDuplicates count]);
return noDuplicates;
}
Create an array called "addedCars" - you will use it to store the name of each unique car.
In each iteration, use [NSArray containsObject:] to check if the current name has already been added to "addedCars". If not, add the name to "addedCars" and the car to "noDuplicates". Otherwise, skip this item, as it has already been added to noDuplicates.
be sure [isEqual:] and [hash] is implemented as you expected
-(NSMutableArray*)removeDuplicateCars:(NSMutableArray*)array{
NSOrderedSet *set = [[NSOrderedSet alloc] initWithArray:array];
NSMutableArray *newArr = [NSMutableArray arrayWithCapacity:[set count]];
for (id obj in set) {
[newArr addObject:obj];
}
return newArr;
}
You used ![car.name isEqualToString:car2.name] to compare objects so I believe you want to filter objects with same name? Than you need to override [isEqual:] for Car
- (BOOL)isEqual:(id)other {
if ([other isKindOfClass:[self class]]) {
return [self.name isEuqalToString: [other name]];
}
return NO;
}
- (NSUInteger)hash {
return [self.name hash];
}
also check this question The best way to remove duplicate values from NSMutableArray in Objective-C?

Resources