2014-10-06 23 views
2

我已经构建了一个小的演示应用程序,它允许用户选择一种颜色,它发送到一个基本的(现在为localhost)node.js服务器(使用NSURLSessionDataTask ),它使用颜色名称来获取水果名称和图像URL,并返回一个简单的包含两个属性的JSON对象。内存泄漏时,NSURLSession调用和加载映像到NSImage

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

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

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

  1. 初始化会话任务,路过一个块作为完成处理。

  2. 如果我理解正确,块在单独的线程上运行,因为默认情况下NSURLSession中的通信是异步的,所以更新GUI必须发生在main中,因此在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

哪里 “fruitImage” 来自于AppDelegate.m?我没有看到它宣布。

行:

__block NSURLSessionDownloadTask *imgTask 

是有点不可思议,因为你标记imgTask作为可以在块改变一个参考,但它也返回值。这可能是你的问题的一部分,但至少目前还不清楚。我可能会争辩说,标记为__block的所有变量都不必如此。

通常这些情况下的内存泄漏是由该块的变量捕获方面引起的,但我没有看到明显的违规者。 “弱自我”模式可能会帮助你。

使用“泄漏”可能会帮助您查看哪些对象泄漏,哪些可以帮助隔离要关注的内容,还可以尝试查看您的块的生命周期。如果一个块被一个对象持有,它可以通过隐式地保留其他对象来创建循环。

请确定发生了什么事情后再跟进。

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