3

I've built a small demo-application which allows the user to choose a color, which is sent to a basic (for now localhost) node.js server (using NSURLSessionDataTask), which uses the color name to get a fruit name and image URL, and return a simple 2 property JSON object containing the two.

When the application receives the JSON response, it creates a sentence with the color name and fruit name to display in the GUI, and then spawns another NSURLSession call (this time using NSURLSessionDownloadTask) to consume the image URL and download a picture of the fruit to also display in the GUI.

Both of these network operations use [NSURLSession sharedSession].

I'm noticing that both the JSON call and more noticeably the image download are leaking significant amounts of memory. They each follow a similar pattern using nested blocks:

  1. Initialize the session task, passing a block as the completion handler.

  2. If I understand correctly, the block is run on a separate thread since the communication in NSURLSession is async by default, so updating the GUI has to happen in the main, so within the completeHandler block, a call to dispatch_async is made, specifying the main thread, and a short nested block that makes a call to update the GUI.

My guess is that either my use of nested blocks, or nesting of GCD calls is causing the issue. Though it's entirely possible my problem is multi-faceted.

Was hoping some of you with more intimate knowledge of how Obj-C manages memory with threads and ARC would be greatly helpful. Relevant code is included below:

AppDelegate.m

#import "AppDelegate.h"
#import "ColorButton.h"

@interface AppDelegate ()

@property (weak) IBOutlet NSWindow *window;

@property (weak) IBOutlet NSImageView *fruitDisplay;
@property (weak) IBOutlet NSTextField *fruitNameLabel;

@property (weak) IBOutlet ColorButton *redButton;
@property (weak) IBOutlet ColorButton *orangeButton;
@property (weak) IBOutlet ColorButton *yellowButton;
@property (weak) IBOutlet ColorButton *greenButton;
@property (weak) IBOutlet ColorButton *blueButton;
@property (weak) IBOutlet ColorButton *purpleButton;
@property (weak) IBOutlet ColorButton *brownButton;

@end

@implementation AppDelegate

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    proxy = [[FruitProxy alloc] init];

}

- (void)applicationWillTerminate:(NSNotification *)aNotification
{
    // Insert code here to tear down your application
}

-(BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender
{
    return YES;
}

/*------------------------------------------------------------------*/

- (IBAction)colorButtonWasClicked:(id)sender
{
    ColorButton *btn = (ColorButton*)sender;

    NSString *selectedColorName = btn.colorName;

    @autoreleasepool {
        [proxy requestFruitByColorName:selectedColorName
                   completionResponder:^(NSString* fruitMessage, NSString* imageURL)
         {
             [self fruitNameLabel].stringValue = fruitMessage;


             __block NSURLRequest *req = [NSURLRequest requestWithURL:[NSURL URLWithString:imageURL]];

             __block NSURLSession *imageSession = [NSURLSession sharedSession];
             __block NSURLSessionDownloadTask *imgTask = [imageSession downloadTaskWithRequest:req
                                                                             completionHandler:
                                                          ^(NSURL *location, NSURLResponse *response, NSError *error)
                                                          {

                                                              if(fruitImage != nil)
                                                              {
                                                                  [self.fruitDisplay setImage:nil];
                                                                  fruitImage = nil;
                                                              }

                                                              req = nil;
                                                              imageSession = nil;
                                                              imgTask = nil;
                                                              response = nil;


                                                              fruitImage = [[NSImage alloc] initWithContentsOfURL:location];
                                                              [fruitImage setCacheMode:NO];


                                                              dispatch_async
                                                              (
                                                               dispatch_get_main_queue(),
                                                               ^{
                                                                   [[self fruitDisplay] setImage: fruitImage];
                                                               }
                                                               );

                                                          }];

             [imgTask resume];

         }];
    }


}


@end

FruitProxy.m

#import "FruitProxy.h"

@implementation FruitProxy


- (id)init
{
    self = [super init];

    if(self)
    {
        return self;
    }
    else
    {
        return nil;
    }
}


- (void) requestFruitByColorName:(NSString*)colorName
             completionResponder:(void( ^ )(NSString*, NSString*))responder
{
    NSString *requestURL = [self urlFromColorName:colorName];
    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:requestURL]];

    session = [NSURLSession sharedSession];


    @autoreleasepool {
        NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:
                                      ^(NSData *data, NSURLResponse *response, NSError *connectionError)
                                      {

                                          NSString *text = [[NSString alloc] initWithData:data
                                                                                 encoding:NSUTF8StringEncoding];

                                          NSDictionary *responseObj = (NSDictionary*)[NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
                                          NSString *fruitName = (NSString*)responseObj[@"fruitName"];
                                          NSString *imageURL = (NSString*)responseObj[@"imageURL"];

                                          NSLog(@"Data = %@",text);

                                          dispatch_async
                                          (
                                           dispatch_get_main_queue(),
                                           ^{
                                               responder([self messageFromColorName:colorName fruitName:fruitName], imageURL);
                                           }
                                           );
                                      }];

        [task resume];
    }

}


- (NSString*)urlFromColorName:(NSString*)colorName
{
    NSString *result;

    result = @"http://localhost:9000/?color=";
    result = [result stringByAppendingString:colorName];

    return result;
}

- (NSString*)messageFromColorName:(NSString*)colorName
                        fruitName:(NSString*)fruitName
{
    NSString *result = @"A ";

    result = [[[[result stringByAppendingString:colorName]
                        stringByAppendingString:@"-colored fruit could be "]
                        stringByAppendingString:fruitName]
                        stringByAppendingString:@"!"];


    return result;
}


@end

1 Answers1

2

Where does "fruitImage" come from in AppDelegate.m? I don't see it declared.

the line:

__block NSURLSessionDownloadTask *imgTask

is a bit weird because you're marking imgTask as a reference that can change in the block, but it's also the return value. That might be part of your problem, but in the very least it's unclear. I might argue that all the variables you marked __block aren't required to be as such.

typically a memory leak in these situations is caused by the variable capture aspect of the block, but I'm not seeing an obvious offender. The "Weak Self" pattern might help you here.

Using "leaks" might help you see what objects are leaking, which can help isolate what to focus on, but also try to take a look at your block's life cycles. If a block is being held by an object it can create cycles by implicitly retaining other objects.

Please follow up when you figure out exactly what's going on.

reference: What does the "__block" keyword mean? Always pass weak reference of self into block in ARC?

Community
  • 1
  • 1
KirkSpaziani
  • 1,962
  • 12
  • 14