2013-05-16 73 views
3

我在NSURLConnection上使用sendSynchronousRequest方法從服務器加載圖像。它在網絡連接速度很快時工作正常,但在連接速度較慢時會崩潰我的應用程序。我究竟做錯了什麼?sendSynchronousRequest在緩慢的網絡連接上崩潰?

[postBody appendData:[[NSString stringWithFormat:@"\r\n--%@\r\n",stringBoundary] dataUsingEncoding:NSUTF8StringEncoding]];    
[postBody appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"image\";filename=\"%@.jpg\"\r\n",uploadImageName] dataUsingEncoding:NSUTF8StringEncoding]];    
[postBody appendData:[[NSString stringWithString:@"Content-Type: application/octet-stream\r\n\r\n"] dataUsingEncoding:NSUTF8StringEncoding]]; 
[postBody appendData:[NSData dataWithData:UIImageJPEGRepresentation(Obj1.thumbImage, 0.5)]]; 
[postBody appendData:[[NSString stringWithFormat:@"\r\n--%@--\r\n",stringBoundary] dataUsingEncoding:NSUTF8StringEncoding]]; 
[postRequest setHTTPBody:postBody]; 

NSData *returndata=[NSURLConnection sendSynchronousRequest:postRequest returningResponse:nil error:nil]; 
NSString *string=[[NSString alloc]initWithData:returndata encoding:NSUTF8StringEncoding]; 

NSError *error; 
NSString *path; 
NSData *imageData; 

if (error != nil && [string isEqualToString:@"success"]) { 
    //doing other work 
} 

我使用後臺線程來運行所有這些代碼。當我在網絡連接速度較慢時上傳更多圖片時,應用程序崩潰。

+1

當你崩潰時你會得到什麼錯誤?並在哪條線崩潰? –

+0

我無法得到它,因爲它發生在罕見的情況。這可能是什麼原因呢? – Xcode

+0

你可以在放置斷點時測試它嗎?代碼似乎沒問題,因爲它在這裏顯示。也許你可以在這裏添加更多的代碼可以解決問題。 –

回答

1

不幸的是,我沒有看到任何明顯的代碼片斷本身,這會導致崩潰。在這個答案的最後,我提供了一個我已經完成的代碼示例,所以也許你可以交叉引用它來反對你的。

一對夫婦的想法:

  1. 你說這個工程上快速連接,但不慢的連接。這是從主線程運行同步查詢的症狀。如果您意外地從主隊列中進行此同步調用,則在較慢的連接上,看門狗進程可能會終止您的應用程序。只要主線程沒有響應,它就會這樣做。

    因此,使用異步調用(如您在其他問題中追求的那樣)或確保這發生在後臺隊列中。

    順便說一句,你說你使用performSelectorInBackground提交到後臺線程。現在大多數人會使用GCD或NSOperationQueue。請參閱Concurrency Programming Guide

  2. 一般來說,如果有例外,我會問你檢查你的一些變量:

    • 你不向我們展示postDataalloc/init。如果這是nil或是NSData而不是NSMutableData,則可能會發生異常。

    • 您應該確保Obj1.thumbImage不是nil。這也可能導致異常。

    坦率地說,如果這個正在快速連接,而不是緩慢的,問題是不太可能在這些變量的基本設置,而不管你是他們在其他地方異步更新(不是同步線程編程指南Synchronization部分中的更新或通過Eliminating Lock-Based Code併發編程指南)中的隊列更新。編寫線程安全代碼時,需要注意同步更改,並且在某些情況下,連接的速度可能會影響行爲。

  3. 如果這兩點不能解決您的問題,那麼我們真的需要深入研究您的例外。不用說,只要你發佈有關異常的任何問題,你必須:

    • 告訴我們的例外是

    • 確定引起異常的行什麼;您可以通過

      • 看堆棧跟蹤來縮小問題的根源做到這一點;

      • 嘗試adding an exception breakpoint;有時候這會識別導致異常的確切線。

      • 如果這樣不起作用,請嘗試單步調試調試器中的代碼。

      • 診斷問題的最不優雅的方式是添加一堆NSLog語句,因此您可以計算出代碼在崩潰之前得到了多少程度,並且各種變量都是您所期望的值。

  4. 如果你原諒觀察,而我很欣賞你試圖從太多的代碼饒了我們(有時人崗噸無關的代碼,所以感謝節約我們),你一般不會共享不足的代碼。十次中有九次,人們正在遭受的錯誤/異常是由於一些簡單的變量沒有被設置爲您認爲它所具有的(例如未初始化等)。因此,您需要包含稍微更完整的代碼示例,或者您的代碼片段應包含NSLogifassert語句,以證明這些值是有效的。

    例如,您可能希望在

    if ([NSThread isMainThread]) 
    { 
        NSLog(@"%s: should not be on main thread!!!", __FUNCTION__); 
    } 
    

    這扔可以保證自己這是在後臺線程上運行。您可能還想在登錄時登錄Obj1.thumbImagepostBody。 (a)發佈一些簡單的代碼,(b)告訴我們它正在崩潰在這個片段的某處(但不是確切的地方)和(c)向我們保證值傳遞給非常簡單的代碼片段都是有效的(儘管代碼片段沒有說明)。簡單的現實,通常不是所有這三個條件在同一時間都是真實的。當人們發佈崩潰時,問題就像在代碼本身傳遞給違規代碼的變量一樣。

幾個不相關的要點:

  1. 順便說一句,在你sendSynchronousRequest,你爲error指定nil。如果API爲您提供診斷錯誤成功或失敗的機會,那麼您應該利用這一點。

  2. 使用UIImageJPEGRepresentation確實是數據丟失嚴重的最壞情況。如果可能的話,回到UIImage的來源(它是如何創建的?NSData?)。如果你不能這樣做,可能會考慮UIImagePNGRepresentation,因爲它仍然具有壓縮功能,但數據丟失較少。有時候你必須做UIImageJPEGRepresentation,但認爲它是最後的手段。

  3. 如果你的服務器只返回「成功」文本字符串,那麼我想這就是你所能做的。如果可能的話,最好是如果你可以編程你的服務器返回一個JSON響應,你可以很容易地使用/解析不同類型的錯誤/響應的不同返回碼。

不管怎麼說,這是一個工作的上傳程序:

- (void)viewDidLoad 
{ 
    [super viewDidLoad]; 

    // [self performSelectorInBackground:@selector(upload) withObject:nil]; 

    NSURL *url = [NSURL URLWithString:@"http://my.url.com/upload.php"]; 
    NSString *path = [[NSBundle mainBundle] pathForResource:@"myimage" ofType:@"jpg"]; // note, I'm going back to bundle, not grabbing `UIImage` from imageview 

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 

     [self uploadFileAtPath:path 
         forField:@"file" 
          URL:url 
        parameters:nil]; 
    }); 
} 

- (NSString *)generateBoundaryString 
{ 
    // generate boundary string 
    // 
    // adapted from http://developer.apple.com/library/ios/#samplecode/SimpleURLConnections 
    // 
    // Note in iOS 6 and later, you can just: 
    // 
    // return [NSString stringWithFormat:@"Boundary-%@", [[NSUUID UUID] UUIDString]]; 

    CFUUIDRef uuid; 
    NSString *uuidStr; 

    uuid = CFUUIDCreate(NULL); 
    assert(uuid != NULL); 

    uuidStr = CFBridgingRelease(CFUUIDCreateString(NULL, uuid)); 
    assert(uuidStr != NULL); 

    CFRelease(uuid); 

    return [NSString stringWithFormat:@"Boundary-%@", uuidStr]; 
} 

- (NSString *)mimeTypeForPath:(NSString *)path 
{ 
    // Get a mime type for an extension using MobileCoreServices.framework. 
    // 
    // You could hard code this instead, but I like using MobileCoreServices as 
    // it increases my code reuse possibilities in the future. 

    CFStringRef extension = (__bridge CFStringRef)[path pathExtension]; 
    CFStringRef UTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, extension, NULL); 
    assert(UTI != NULL); 

    NSString *mimetype = CFBridgingRelease(UTTypeCopyPreferredTagWithClass(UTI, kUTTagClassMIMEType)); 
    assert(mimetype != NULL); 

    CFRelease(UTI); 

    return mimetype; 
} 

- (void)uploadFileAtPath:(NSString *)imagePath 
       forField:(NSString *)fieldName 
        URL:(NSURL*)url 
       parameters:(NSDictionary *)parameters 
{ 
    NSString *filename = [imagePath lastPathComponent]; 
    NSData *imageData = [NSData dataWithContentsOfFile:imagePath]; 

    NSMutableData *httpBody = [NSMutableData data]; 
    NSString *boundary = [self generateBoundaryString]; 
    NSString *mimetype = [self mimeTypeForPath:imagePath]; 

    // configure the request 

    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url]; 
    [request setCachePolicy:NSURLRequestReloadIgnoringLocalCacheData]; 
    [request setHTTPShouldHandleCookies:NO]; 
    [request setTimeoutInterval:30]; 
    [request setHTTPMethod:@"POST"]; 

    // set content type 

    NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary]; 
    [request setValue:contentType forHTTPHeaderField: @"Content-Type"]; 

    // add params (all params are strings) 

    [parameters enumerateKeysAndObjectsUsingBlock:^(NSString *parameterKey, NSString *parameterValue, BOOL *stop) { 
     [httpBody appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]]; 
     [httpBody appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n", parameterKey] dataUsingEncoding:NSUTF8StringEncoding]]; 
     [httpBody appendData:[[NSString stringWithFormat:@"%@\r\n", parameterValue] dataUsingEncoding:NSUTF8StringEncoding]]; 
    }]; 

    // add image data 

    if (imageData) { 
     [httpBody appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]]; 
     [httpBody appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"\r\n", fieldName, filename] dataUsingEncoding:NSUTF8StringEncoding]]; 
     [httpBody appendData:[[NSString stringWithFormat:@"Content-Type: %@\r\n\r\n", mimetype] dataUsingEncoding:NSUTF8StringEncoding]]; 
     [httpBody appendData:imageData]; 
     [httpBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]]; 
    } 

    [httpBody appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]]; 

    // setting the body of the post to the reqeust 

    [request setHTTPBody:httpBody]; 

    NSError *error; 
    NSData *returndata = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:&error]; 

    if (error) 
    { 
     NSLog(@"error=%@", error); 
     return; 
    } 

    // I generally return JSON response, so this is where I parse it; 
    // obviously, your validation process will differ. 

    NSDictionary *results = [NSJSONSerialization JSONObjectWithData:returndata options:0 error:&error]; 

    if (error) 
     NSLog(@"error=%@", error); 
    if (results) 
     NSLog(@"result=%@", results); 

} 
0

跟蹤下來...

添加一個異常斷點。這將停止正在發生異常的線上的調試器。

後臺線程沒有自己的異常處理程序,所以它們會生成一個信號並使您的進程崩潰。所以你可以添加自己的東西,特別是對於這樣的情況,系統調用正在生成異常,而你沒有一個好的方法來阻止它。

@try{ 

    NSData *returndata=[NSURLConnection sendSynchronousRequest:postRequest returningResponse:nil error:nil]; 
    NSString *string=[[NSString alloc]initWithData:returndata encoding:NSUTF8StringEncoding]; 

} 

@catch(NSException *e) 
{ 
    NSLog(@"caught exception: %@",e); 
    //you could also iterate over: - (NSArray *)callStackSymbols NS_AVAILABLE(10_6, 4_0); 
}