Hi everyone,
I'm stuck these days on some memory leaks. The app I'm making is working like that :
1 - Loads a file into memory
2 - Create a screen according to some values read on that file
3 - Display the view
Far from now everything is normal when I start the app and get the first screen. There is no leaks.
But when I want to load an other screen from the current view I got plenty of leaks from autoreleased objects. And I don't understand because when I load a new view from the current one the process is similar :
1 - Desctruction of the current view
2 - Loads a file into memory
3 - Create a screen according to some values read on that file
4 - Display the view
Here are some concrete example of what is leaking :
-(NSString*)pathForApplicationName:(NSString*)appName
withImage:(NSString*)imagePath {
NSArray *searchPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentPath = [searchPaths lastObject];
return [NSString stringWithFormat:#"%#/%#/assets/www/%#",documentPath,[self convertSpacesInDashes:appName],imagePath];
}
The stringWithFormat:.. is leaking. An other example :
-(UIColor*)convertHexColorToUIColor:(NSString*)hexColor {
if ([hexColor length] == 7) {
unsigned c = 0;
NSScanner *scanner = [NSScanner scannerWithString:hexColor];
[scanner setScanLocation:1];
[scanner scanHexInt:&c];
return [UIColor colorWithRed:((c>>16)&0xFF)/255.0
green:((c>>8)&0xFF)/255.0
blue:(©&0xFF)/255.0
alpha:1.];
}
else {
return nil;
}
}
Same, colorWithRed:.. is leaking.
I've read apple's documentation regarding autoreleased objects. And I've even tried to recreate a new pool like that, without any success :
-(UIView)myFonctionWhoGenerateScreens:
for () {
NSAutoreleasePool *subPool = [[NSAutoreleasePool alloc] init];
// There are all of the autoreleased method calls that are leaking...
[subPool drain];
}
}
I think I am missing something. Does anyone has an idea?
Leak backtrace
Thanks a lot.
Edit :
Here is how the return of the leaking function is handled in applyPropertyName:withPropertyType:onComponent:withValue:forStyling method :
else if ([propertyType isEqualToString:#"AB_IMAGE"]) {
UIImage *image = [UIImage imageWithContentsOfFile:[self pathForApplicationName:applicationName
withImage:value]];
#try {
[component setValue:image
forKey:propertyName];
}
#catch (NSException *exception) {
#if DEBUG_CONTROLLER
NSLog(#" %# Not key-value compliant for <%#,%#>",component,propertyName,image);
#endif
}
}
Edit 2 :
Here is the complete back trace http://ganzolo.free.fr/leak/%20Leak.zip
Looking at your Leak document, and picking the [NSString stringWithFormat] object at address 0xdec95c0 as an example, it shows balanced retain count operations for the Foundation, ImageIO, and CoreGraphics use of the object but ABTwoImageItemImageLeftComponent is still holding an unreleased reference.
0 0xdec95c0 CFString (immutable) Malloc 1 00:39.994.343 144 Foundation +[NSString stringWithFormat:]
1 0xdec95c0 CFString (immutable) Autorelease <null> 00:39.994.376 0 Foundation +[NSString stringWithFormat:]
2 0xdec95c0 CFString (immutable) CFRetain 2 00:39.994.397 0 iOS Preview App -[ABTwoImageItemImageLeftComponent setSrcProperty:]
3 0xdec95c0 CFString (immutable) CFRetain 3 00:39.996.231 0 ImageIO CGImageReadCreateWithFile
4 0xdec95c0 CFString (immutable) CFRetain 4 00:39.998.012 0 CoreGraphics CGPropertiesSetProperty
5 0xdec95c0 CFString (immutable) CFRelease 3 00:40.362.865 0 Foundation -[NSAutoreleasePool release]
6 0xdec95c0 CFString (immutable) CFRelease 2 01:14.892.330 0 CoreGraphics CGPropertiesRelease
7 0xdec95c0 CFString (immutable) CFRelease 1 01:14.892.921 0 ImageIO releaseInfoJPEG
Related
I'm loading a CSV file that I know nothing about except that the first row are column headers, which could be anything. The number of columns is unknown too. I tried to add a sortDescriptorPrototype to each column and when I do I get up/down arrows visible in the TableView.
for(NSTableColumn * col in self.tableView.tableColumns){
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey: col.title ascending: YES];
col.sortDescriptorPrototype = sortDescriptor;
}
Each time I click on the column sortDescriptorsDidChange is called:
- (void)tableView:(NSTableView *)tableView sortDescriptorsDidChange:(NSArray *)oldDescriptors
{
[_data sortUsingDescriptors: [tableView sortDescriptors]];
[tableView reloadData];
}
_data is a NSMutableArray where data is stored and first row are the header names. Each row is another NSArray Unfortunately I get this error:
2017-04-17 12:35:09.979142+0200 Table Tool[20954:2577488] [General] [<__NSCFString 0x610000474840> valueForUndefinedKey:]: this class is not key value coding-compliant for the key beds.
2017-04-17 12:35:09.982161+0200 Table Tool[20954:2577488] [General] (
0 CoreFoundation 0x00007fffb31a837b __exceptionPreprocess + 171
1 libobjc.A.dylib 0x00007fffc7f9c48d objc_exception_throw + 48
2 CoreFoundation 0x00007fffb31a82c9 -[NSException raise] + 9
3 Foundation 0x00007fffb4c83e5e -[NSObject(NSKeyValueCoding) valueForUndefinedKey:] + 226
4 Foundation 0x00007fffb4b55d9c -[NSObject(NSKeyValueCoding) valueForKey:] + 283
5 Foundation 0x00007fffb4b59aac -[NSArray(NSKeyValueCoding) valueForKey:] + 467
6 Foundation 0x00007fffb4b55b79 -[NSArray(NSKeyValueCoding) valueForKeyPath:] + 448
7 Foundation 0x00007fffb4b7cc9f _sortedObjectsUsingDescriptors + 371
8 Foundation 0x00007fffb4be94f7 -[NSMutableArray(NSKeyValueSorting) sortUsingDescriptors:] + 468
9 Table Tool 0x00000001000051d8 -[Document tableView:sortDescriptorsDidChange:] + 184
10 AppKit 0x00007fffb0f05425 -[NSTableView setSortDescriptors:] + 260
11 AppKit 0x00007fffb13c9fbc -[NSTableView _changeSortDescriptorsForClickOnColumn:] + 480
Obviously I have read a lot on the topic, but all examples assume that you know column names at design-time. I'm interested in a solution with NSSortDescriptor and sortUsingDescriptors in Obj-C or Swift, some explanation on sortDescriptorPrototype and why you need it in the first place, since sortDescriptor changes when I click on the column. Is there anything in KVC I can use for the sorting key, like "self" (there is ...valueForKeyPath:#"#sum.self"...)?
Some useful links:
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/TableView/SortingTableViews/SortingTableViews.html
https://www.raywenderlich.com/143828/macos-nstableview-tutorial
Here is a completely moronic way of solving it that partly works:
First I extract the column index with this method:
-(NSUInteger)getColumnIndexFromTableViewSortDescriptor {
for(NSUInteger i = 0; i < _maxColumnNumber; i++){
if(_tableView.tableColumns[i].title == _tableView.sortDescriptors[0].key){
return i;
}
}
assert(false && "Missing column name");
return 0;
}
Then I sort the array in-place with sortUsingComparator. Surely this has to be the "wrong". It's completely idiotic to use sortDescriptors if they are not used, other than to figuring out which column was clicked. I guess the click event could figure that out too?
-(void)tableView:(NSTableView *)tableView sortDescriptorsDidChange: (NSArray *)oldDescriptors
{
NSUInteger idx = self.getColumnIndexFromTableViewSortDescriptor;
if(tableView.sortDescriptors[0].ascending){
[_data sortUsingComparator: ^(id obj1, id obj2) {
return [[obj2 objectAtIndex:idx] compare:[obj1 objectAtIndex:idx]];
}];
} else {
[_data sortUsingComparator: ^(id obj1, id obj2) {
return [[obj1 objectAtIndex:idx] compare:[obj2 objectAtIndex:idx]];
}];
}
[tableView reloadData];
}
Now the CSV data I'm importing the data in the columns is not uniform, so it could be a NSNumber, followed by three NSDecimalNumber and a few NSString. Partly that's a problem with the data and/or the CSV parser I'm using (ripped from the web, but I will rewrite the sucker later).
So you could end up comparing NSDecimalNumber with NSString which will generate errors like:
unrecognized selector sent to instance
So I continued my stupidity with:
#interface NSDecimalNumber (CompareString)
-(NSComparisonResult)compare:(NSString *)string;
#end
#implementation NSDecimalNumber (CompareString)
-(NSComparisonResult)compare:(NSString *)string{
NSString *decimalString = self.stringValue;
return[decimalString compare:string options:NSCaseInsensitiveSearch];
}
Now if you compare two NSDecimalNumber the above will be called and the parameter "string" NSDecimalNumber, even though "string" declared NSString, resulting in "unrecognized selector sent to instance" once again.
I have an NSMutableArray getting populated with values within a enumerateObjects loop. About the 4th or 5th time the function to populate the MutableArray is getting called, I get a SIGSEGV error with the stack
0 libobjc.A.dylib 0x39e7e626 objc_msgSend + 6
1 CoreFoundation 0x2f663dc9 -[__NSArrayM dealloc] + 154
2 libobjc.A.dylib 0x39e83b6b _ZN11objc_object17sidetable_releaseEb + 172
3 MyAppName 0x0004bdff -[BluetoothManager(DataParse) processResponse:] (BluetoothManager+DataParse.m:231)
Setup
An appDelegate which has a strong pointer to BluetoothManager instance.
A function call on BluetoothManager class, with the signature processResponse: on BluetoothManager which is invoked every time didUpdateValueForCharacteristic: is called on the peripheral delegate.
processBleResponse: takes an NSData blob and converts it into an array of NSNumbers
What I'm noticing is that the 4th time or so that processReponse: is called, the mutable Array in which I store all my NSNumbers gets malloc'd within its own enumerateObjects loop.
I've simulated the test with a repeating NSTimer which invokes processResponse: every 30-40s with some dummy data, but the error is reproducible after every few iterations of the loop. The overall memory usage of the app is not spiking over 45MB
// method on BluetoothManager class
- (void)processBleResponse:(NSData *)myData
{
__block float baseValue = 34.500;
__block float incrementValue = 0.0500;
// convert hex data into array of 10 binary "strings"
NSArray *individualBinaryArray = [self extractBinaryValues:myData];
// Extract temperature fields
NSIndexSet *relevantIndices = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(1, individualBinaryArray.count - 1)];
__block NSUInteger indexOfFailure = NSNotFound;
NSMutableArray *decimalValues = [[NSMutableArray alloc] init];
[individualBinaryArray enumerateObjectsAtIndexes:relevantIndices options:NSEnumerationConcurrent usingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSString *hexString = [self hexString:(NSString *)obj];
if (hexString.length)
{
NSScanner *decimalScanner = [NSScanner scannerWithString:hexString];
unsigned int decimalValue = 0;
if ([decimalScanner scanHexInt:&decimalValue])
{
// insert value
// THIS IS THE LINE WHERE THE CRASH OCCURS
[decimalValues addObject:#(baseValue + incrementValue * decimalValue)];
}
else
{
// throw alert/exception etc.
*stop = YES;
indexOfFailure = idx;
}
}
else
{
*stop = YES;
indexOfFailure = idx;
}
}];
// Insert list of NSNumbers into CoreData
}
What are the advantages and disadvantages of the following two approaches:
enumerate using block
NSArray *myArray = [NSArray new];
[myArray enumerateObjectsUsingBlock:^(id anObject, NSUInteger idx, BOOL *stop) {
if (anObject == someOtherObject) {
[anObject doSomething:idx];
*stop = YES;
}
}];
fast enumeration
NSArray *myArray = [NSArray new];
int idx = 0
for (id anObject in myArray) {
if (anObject == someOtherObject) {
[anObject doSomething:idx];
break;
}
++idx;
}
This blog post covers the major differences. In summary:
Fast enumeration is available on OS X 10.5+, blocks are available on 10.6+
For simple enumeration, fast enumeration is a bit faster than block-based enumeration
It's easier (and more performant) to do concurrent or reverse enumeration with block-based enumeration than with fast enumeration
When enumerating over an NSDictionary you can get key and value in one hit with a block-based enumerator, whereas with fast enumeration you have to use the key to retrieve the value in a separate message send.
Regarding the last point (NSDictionary enumeration), compare this:
for (id key in dictionary)
{
id obj = [dictionary objectForKey: key];
// do something with key and obj
}
to this:
[dictionary enumerateKeysAndObjectsUsingBlock: ^(id key, id obj, BOOL *stop) {
// do something with key and obj
}];
In addition, both methods protect mutable collections from mutation inside the enumeration loop. Interestingly, if you try to mutate the collection inside a block-based enumeration, you get an exception thrown by CoreFoundation's __NSFastEnumerationMutationHandler, suggesting that there's some common code under the hood.
NSMutableArray *myArray = [NSMutableArray arrayWithObjects:#"a", #"b", nil];
[myArray enumerateObjectsUsingBlock:^(id anObject, NSUInteger idx, BOOL *stop) {
// Attempt to mutate the array during enumeration
[myArray addObject:#"c"];
}];
Output:
2011-12-14 22:37:53.716 Untitled[5809:707] *** Terminating app due to uncaught exception 'NSGenericException', reason: '*** Collection <__NSArrayM: 0x109614190> was mutated while being enumerated.'
*** First throw call stack:
(
0 CoreFoundation 0x00007fff8cca7286 __exceptionPreprocess + 198
1 libobjc.A.dylib 0x00007fff8319ad5e objc_exception_throw + 43
2 CoreFoundation 0x00007fff8cd311dc __NSFastEnumerationMutationHandler + 172
3 CoreFoundation 0x00007fff8cc9efb4 __NSArrayEnumerate + 612
4 Untitled 0x00000001094efcea main + 250
5 Untitled 0x00000001094efbe4 start + 52
6 ??? 0x0000000000000001 0x0 + 1
)
terminate called throwing an exceptionRun Command: line 1: 5809 Abort trap: 6 ./"$2"
First thoughts that come to my mind
Blocks are available in iOS 4 and later so if you need to support older versions then you can not use the block syntax.
They are pretty equivalent in terms of what they do apart from you can't accidentally mess up the counter in the block version.
One other potential difference is that you can define the block elsewhere and pass in different blocks dependant on your state.
Hopefully this was just a very rough example as the code snippet is pretty poor and there are more efficient way of doing this ;)
I'm working on an iPad app that has a slider that is used to scroll through data. When scrolling, a map is displayed and the data is updated. The problem is, if you scroll fast enough (or somehow trigger the race condition), the app crashes on accessing a zombie NSString. I've been able to track it down in the Profiler and found this:
Event Type RefCt Timestamp Size Responsible Library Responsible Caller
Malloc 1 01:55.166.466 16 Foundation -[NSPlaceholderString initWithFormat:locale:arguments:]
Autorelease <null> 01:55.166.472 0 Foundation +[NSString stringWithFormat:]
CFRetain 2 01:55.166.473 0 My Program -[StateView updateVotes:]
CFRetain 3 01:55.166.476 0 UIKit -[UILabel setText:]
CFRelease 2 01:55.166.504 0 My Program -[StateView updateVotes:]
CFRelease 1 01:55.177.661 0 Foundation -[NSAutoreleasePool release]
CFRelease 0 01:55.439.090 0 UIKit -[UILabel setText:]
Zombie -1 01:55.439.109 0 UIKit -[NSString(UIStringDrawing) drawAtPoint:forWidth:withFont:lineBreakMode:letterSpacing:includeEmoji:]
I'm using ARC on iOS5, so I'm not in control of the retain/release at all. Even if I was, looking at the above, it is correct. The problem seems to be a race condition between the drawing function and the UILabel string actually changing. The UILabel releases the first string, as a new one has been set, but the drawing function is holding a reference to it somehow, but did not retain it.
As a note, I have not modified the UILabel in any way.
Any ideas?
--- Code added as update:
Slider update:
-(void)sliderValueChanged:(UISlider *)slider {
float position = slider.value - 1790.0f;
int year;
if(position <= 0.0f) {
year = 1789;
} else {
year = 1792 + (floor(position / 4.0f)*4);
}
[self setYear:year];
}
setYear:
-(void)setYear:(int)year {
if (year == currentYear) {
// year didn't change, so don't do anything
return;
}
[yearLabel setText:[[NSString alloc] initWithFormat:#"%i", year]];
currentYear = year;
[self getMapForYear:year];
}
getMapForYear:
-(void) getMapForYear:(int)year {
[self setToMap:[historicalData objectForKey:[NSNumber numberWithInt:year]];
}
setToMap:
-(void) setToMap:(HistoricalMap *)map {
// Label the map
for (State *state in [map states]) {
[mapController setVotes:[state votes] forState:[state abbreviation]];
}
}
setVotes:forState:
-(void)setVotes:(NSNumber *)votes forState:(NSString *)stateAbbreviation {
StateView *state = [states objectForKey:stateAbbreviation];
if (state == nil) {
NSLog(#"Invalid State Votes -- %#", stateAbbreviation);
return;
}
[state updateVotes:votes];
[state setNeedsDisplay];
}
updateVotes:
-(void)updateVotes:(NSNumber *)newVotes {
[self setVotes:newVotes];
NSString *voteString = [[NSString alloc] initWithFormat:#"%#", newVotes];
[voteLabel setText:voteString];
if ([newVotes isEqual:[NSNumber numberWithInt:0]]) {
[[self voteLabel] setHidden:YES];
[[self stateAbbreviationLabel] setHidden:YES];
} else {
[[self stateAbbreviationLabel] setHidden:NO];
[[self voteLabel] setHidden:NO];
}
}
I think you are attempting to do far too much during the slider's movement. Creating and executing core data fetch requests alone would seem to be overkill, let alone updating the entire GUI and a screenful of labels. Have you tested the performance of this on a device?
It could be worth profiling these sections of code and seeing where the time is spent. You could look at caching the fetch requests or the results, for example, or you may have to resort to updating only when the slider has stopped, or only for every n increments along the path.
You havw several memory-leaks with NSString:
[yearLabel setText:[[NSString alloc] initWithFormat:#"%i", year]]; // leak
Create string with stringWithFormat method instead
[yearLabel setText:[NSString stringWithFormat:#"%i", year]];
[NSString stringWithFormat: **is the best way formatting the string than any other..**
I got this symbolicated stack trace from a crash report from my iPad app (excerpt):
Exception Type: EXC_CRASH (SIGABRT)
Exception Codes: 0x00000000, 0x00000000
Crashed Thread: 0
0 ImageIO 0x34528eb4 _CGImagePluginIdentifyPNG + 0
1 ImageIO 0x34528d90 _CGImageSourceBindToPlugin + 368
2 ImageIO 0x34528bda CGImageSourceGetCount + 26
3 UIKit 0x341b8f66 _UIImageRefAtPath + 366
4 UIKit 0x342650ce -[UIImage initWithContentsOfFile:] + 50
5 UIKit 0x342b0314 +[UIImage imageWithContentsOfFile:] + 28
6 DesignScene 0x00013a2a -[LTImageCache fetchImageforURL:] (LTImageCache.m:37)
…
Here are the contents of -[LTImageCache fetchImageforURL:]:
- (UIImage *)fetchImageforURL:(NSString *)theUrl {
NSString *key = theUrl.md5Hash;
return [UIImage imageWithContentsOfFile:[self filenameForKey:key]];
}
And the contents of -[LTImageCache filenameForKey:]:
- (NSString *) filenameForKey:(NSString *) key {
return [_cacheDir stringByAppendingPathComponent:key];
}
The _cacheDir ivar is created and retained in -init. So the question is, what caused this crash? Is the problem that:
the return value of -[LTImageCache filenameForKey:] needs to be retained (it's autoreleased)
An unhandled exception somewhere (+[UIImage imageWithContentsOfFile:] claims to return nil if the image is unrecognizable)
Something else…I'm out of guesses
I would think that an autoreleased value would be fine. An in truth, this code has been working fine for months, and this method is called 100s of times in a session. This is a rare crash under very specific circumstances (the app left loaded overnight, crash upon unlocking the iPad in the morning).
What's causing this crash?
I'm guessing but it looks like a bogus image file. Is this in your app bundle or do you download it?
I don't think it has anything to do with memory mgt.
To test you could try using ImageIO to open the file yourself.
CGImageSourceRef imageSource = CGImageSourceCreateWithURL((CFURLRef)self.url, NULL);
if(NULL != imageSource) {
size_t imageCount = CGImageSourceGetCount(imageSource);
if(imageCount > 0) {
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], kCGImageSourceCreateThumbnailFromImageIfAbsent,
[NSNumber numberWithInteger:maxSize], kCGImageSourceThumbnailMaxPixelSize, nil];
CGImageRef thumbImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, (CFDictionaryRef)options);
self.image = [UIImage imageWithCGImage:thumbImage scale:scale orientation:imageOrientation];
CGImageRelease(thumbImage);
CFRelease(imageSource);
[pool drain];
}
} else {
NSLog(#"Unable to open image %#", self.url);
}
and then try to find the image count.
Using maxSize and getting a thumbnail will ensure you don't load a 5 mega-pixel image to put into a 100x100 tile on your UI.
scale is the window's scale (will be 2 for iPhone 4 and 1 for everything else).
To find the orientation you need to use the CGImageSourceCopyPropertiesAtIndex function and then the kCGImagePropertyOrientation key to get the orientation of the particular image.