2014-10-06 26 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?