سؤال

I am parsing data from a XML file and believe I've found a retain cycle. I don't have the best understanding how to fix it though. In short, I'm parsing out a bunch of floats within a block which leads to my memory usage going through the roof - it goes over 250 MB. Not good. Here is the code:

[TBXML iterateElementsForQuery:@"library_animations" fromElement:rootElement withBlock:^(TBXMLElement *anElement) {
        [self parseLibraryAnimationsElement:anElement];
}];

The method above calls the following method:

- (void)parseLibraryAnimationsElement:(TBXMLElement *)element
{
    [TBXML iterateElementsForQuery:@"animation" fromElement:element withBlock:^(TBXMLElement *anElement) {
        TBXMLElement *extraElement = [TBXML childElementNamed:@"extra" parentElement:anElement];
        TBXMLElement *techniqueElement = [TBXML childElementNamed:@"technique" parentElement:extraElement];

        // grab float data
        **NSData *floatData = [self extractFloatArrayFromElement:techniqueElement];**

        // append data per animation ID
        NSString *key = [NSString stringWithFormat:@"#%@", [TBXML valueOfAttributeNamed:@"id" forElement:anElement]];
        [self.root.sortedAnimationKeys addObject:key];

        // use float data to store color information for this frame
        for (COLLADAMeshGeometry *geometry in self.root.geometries.allValues) {
            AGLKMesh *mesh = geometry.mesh;

            NSMutableData *dynamicColorData = [NSMutableData data];
            GLKVector3 *newColorInfo = (GLKVector3 *)[floatData bytes];

            // cycle through vertices for this mesh and use color float array information
            for (int i = 0; i < mesh.numberOfVertices; i++) {
                AGLKMeshVertex *staticVertex = [mesh vertexAtIndex:i];
                GLKVector3 newColor = newColorInfo[staticVertex->colorIndex];
                GLKVector4 color = GLKVector4Make(newColor.x, newColor.y, newColor.z, 1.0);

                [dynamicColorData appendBytes:&color length:sizeof(color)];
            }

            // get key and store in animation dictionary on the mesh
            [mesh.animationBufferDictionary setObject:dynamicColorData forKey:key];
        }
    }];

    // sort animation keys
    if (self.root.sortedAnimationKeys.count > 0) {
        [self.root.sortedAnimationKeys sortUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
    }
}

The method between ** ... ** is also below. This is where I believe the issue is. The strings aren't being deallocated properly.

- (NSData *)extractFloatArrayFromElement:(TBXMLElement *)element
{
    // element is <source>
    NSMutableData *floatData = [NSMutableData data];
    TBXMLElement *floatElement = [TBXML childElementNamed:@"float_array" parentElement:element];
    NSString *stringValues = [TBXML textForElement:floatElement];

    NSArray *values = [stringValues componentsSeparatedByString:@" "];
    for (NSString *value in values) {
        float floatValue = [value floatValue];
        [floatData appendBytes:&floatValue length:sizeof(floatValue)];
    }

    return floatData;
}

I say it's somewhere in here because when I'm using the Allocations Instrument I get a huge number of CFStrings as you can see in the screenshot below. I'm not sure if this is enough information or not, but if someone can see any issues let me know. Otherwise maybe there's a step in the right direction I can take. enter image description here

When I drill down into the CFString (immutable) line, it has a number of lines that these are created in [NSString componentsSeparatedByString:].

هل كانت مفيدة؟

المحلول

Change this:

- (NSData *)extractFloatArrayFromElement:(TBXMLElement *)element
{
    // element is <source>
    NSMutableData *floatData = [NSMutableData data];
    TBXMLElement *floatElement = [TBXML childElementNamed:@"float_array" parentElement:element];
    NSString *stringValues = [TBXML textForElement:floatElement];

    NSArray *values = [stringValues componentsSeparatedByString:@" "];
    for (NSString *value in values) {
        float floatValue = [value floatValue];
        [floatData appendBytes:&floatValue length:sizeof(floatValue)];
    }

    return floatData;
}

To this:

- (NSData *)extractFloatArrayFromElement:(TBXMLElement *)element
{
@autoreleasepool {
    // element is <source>
    NSMutableData *floatData = [NSMutableData data];
    TBXMLElement *floatElement = [TBXML childElementNamed:@"float_array" parentElement:element];
    NSString *stringValues = [TBXML textForElement:floatElement];

    NSArray *values = [stringValues componentsSeparatedByString:@" "];
    for (NSString *value in values) {
        float floatValue = [value floatValue];
        [floatData appendBytes:&floatValue length:sizeof(floatValue)];
    }

    return floatData;
}
}

This assumes that componentsSeparatedByString: is the source of a ton of autoreleased objects. If that doesn't help, then:

  • sort the report in the Allocations Instrument by # of bytes
  • turn on "only track live objects" (or whatever it is called)
  • turn on "track reference count events"
  • click through to the objects and then click through to the creation of a single object

That should tell you what in your code is triggering the allocation. From there, it is a matter of making the memory usage more efficient.

نصائح أخرى

That might be very well auto-released objects hanging around in some autorelease pool.

In order to release them, place autorelease pools inside loops, or possibly inside your extractFloatArrayFromElement method.

Another measurement to avoid auto-released object creation is to avoid class factory methods, e.g.:

NSMutableData* autoreleasedObject = [NSMutableData data];

Instead use:

NSMutableData* data = [[NSMutableData alloc] init];
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top