在进行NSURLSeesion调用和加载图像到NSImage时存在内存泄漏问题

3
我已经建立了一个小型演示应用程序,允许用户选择一种颜色,该颜色将被发送到基本(目前为本地主机)的node.js服务器(使用NSURLSessionDataTask),该服务器使用颜色名称获取水果名称和图像URL,并返回包含这两个属性的简单的2个JSON对象。

当应用程序接收到JSON响应时,它创建一个带有颜色名称和水果名称的句子,以在GUI中显示,然后产生另一个NSURLSession调用(这次使用NSURLSessionDownloadTask)来消耗图像URL并下载水果图片以在GUI中显示。

这两个网络操作都使用[NSURLSession sharedSession]。

我注意到JSON调用和更明显的是图像下载都泄漏了大量的内存。 它们都遵循类似的模式,使用嵌套块:

  1. 初始化会话任务,传递块作为完成处理程序。

  2. 如果我理解正确,由于NSURLSession中的通信默认为异步,因此块在单独的线程上运行,因此必须在主线程中更新GUI,因此在completeHandler块内部,调用dispatch_async,指定主线程,并创建一个简短的嵌套块,用于调用更新GUI的方法。

我的猜测是,我的嵌套块使用或GCD调用的嵌套导致了这个问题。 尽管我的问题可能是多方面的。

希望一些对Obj-C如何处理线程和ARC具有更深入了解的人会非常有帮助。 以下是相关代码:

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个回答

2
在AppDelegate.m文件中,“fruitImage”是从哪里来的?我没有看到它被声明。
代码行:
__block NSURLSessionDownloadTask *imgTask

有点奇怪,因为你将imgTask标记为可以在块中更改的引用,但它也是返回值。这可能是问题的一部分,但至少不清楚。我认为你标记为__block的所有变量都不需要被标记。在这些情况下,内存泄漏通常是由于块的变量捕获方面引起的,但我没有看到明显违规行为。"弱引用自身"模式可能会对你有所帮助。使用"leaks"可能会帮助你看到哪些对象正在泄漏,这可以帮助你排除需要关注的部分,但也请尝试查看块的生命周期。如果一个块被对象持有,它可能会通过隐式保留其他对象来创建循环。找出发生了什么,请跟进。 参考:“__block”关键字的含义是什么? 在ARC中始终将自己的弱引用传递到块中?

参考链接非常有帮助。谢谢! - Mihir Oza

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接