3

我正在使用sendSynchronousRequest方法NSURLConnection从服务器加载图像。当网络连接速度很快时它可以正常工作,但是当连接速度很慢时我的应用程序会崩溃。我究竟做错了什么?

[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
}

我使用后台线程来运行所有这些代码。当我在网络连接速度慢的情况下上传更多图像时,应用程序崩溃。

4

2 回答 2

2

可悲的是,我在代码片段本身中看不到任何会导致崩溃的明显内容。在这个答案的最后,我提供了一个我所做的代码示例,所以也许你可以将它与你的交叉引用。

几个想法:

  1. 您说这适用于快速连接,但不适用于慢速连接。这是从主线程运行同步查询的症状。如果您不小心从主队列进行了此同步调用,则在连接速度较慢的情况下,看门狗进程可能会杀死您的应用程序。只要主线程没有响应,它就会这样做。

    因此,使用异步调用(正如您在其他问题中所追求的那样)或确保这发生在后台队列中。

    顺便说一句,您说您使用performSelectorInBackground. 现在大多数人会使用 GCD 或NSOperationQueue. 请参阅并发编程指南

  2. 一般来说,如果有例外,我会要求您检查一些变量:

    • 您没有向我们展示alloc. 如果那是或者是 a而不是 a ,你可能会得到一个例外。initpostDatanilNSDataNSMutableData

    • 您应该确保Obj1.thumbImage不是nil. 这也可能导致异常。

    坦率地说,如果这适用于快速连接而不是慢速连接,那么问题不太可能出在这些变量的基本设置中,而是您是否在其他地方异步更新它们(而不是按照同步部分同步更新)线程编程指南或队列通过消除并发编程指南中的基于锁的代码)。在编写线程安全代码时,您需要注意同步更改,并且在某些情况下,连接速度会影响行为。

  3. 如果这两点都不能解决你的问题,那么我们真的需要深入挖掘你的异常。不用说,每当您发布有关异常的任何问题时,您都必须:

    • 告诉我们异常是什么

    • 识别导致异常的行;你可以这样做

      • 查看堆栈跟踪以缩小问题的根源;

      • 尝试添加异常断点;有时这将识别导致异常的精确行。

      • 如果这不起作用,请尝试单步执行调试器中的代码。

      • 诊断问题的最不优雅的方法是添加一堆NSLog语句,这样您就可以弄清楚代码在崩溃之前已经走了多远,并且各种变量都是您期望的值。

  4. 如果你原谅我的观察,虽然我很感激你试图让我们免于过多的代码(有时人们会发布大量不相关的代码;所以感谢你饶恕了我们),但你通常没有共享足够的代码。十分之九,人们遭受的错误/异常是由于某些简单变量未设置为您认为的值(例如未初始化等)的结果。因此,您要么需要包含更完整的代码示例,要么您的代码片段应包含NSLogifassert表明这些值有效的语句。

    例如,您可能想输入一个

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

    这可以确保您自己这是在后台线程上运行的。您可能还想在登录Obj1.thumbImage时登录postBody

    我已经看到您的一些问题,您 (a) 发布了一些简单的代码,(b) 告诉我们它在这个片段中的某个地方崩溃了(但不是确切的位置),并且 (c) 向我们保证传递给非常简单的代码片段都是有效的(即使片段没有说明这一点)。一个简单的现实,通常不是所有这三个条件都可以同时成立。当人们发布崩溃时,问题往往出在传递给违规代码的变量中,就像代码本身一样。

一些不相​​关的点:

  1. 顺便说一句,在您的sendSynchronousRequest, 您nil指定error. 如果 API 让您有机会诊断错误的成功或失败,您应该利用它。

  2. 使用UIImageJPEGRepresentation真的是最坏的情况,会大量丢失数据。如果人为可能,请回到源头UIImage(它是如何创建的?a 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);

}
于 2013-05-20T17:07:16.263 回答
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);
}
于 2013-05-20T17:48:51.123 回答