从iPhone上传文件时出现POSIX错误12(“无法分配内存”)

10

我正在开发一个iPhone应用程序,涉及上传相机拍摄的完整照片(通常每张照片大小在1.5到2.0 MB之间)以及它们的缩略图(小得多)到Amazon S3。

缩略图总是成功上传,但是完整的图片有时候不会上传,并且当它们失败时,它们会出现POSIX错误代码12,即ENOMEM。然而,我已经添加了调试代码来打印错误发生时的可用内存量,而且通常有很多空闲内存,通常超过100 MB。

此外,当上传通过3G进行时,错误更容易出现,而当上传通过WiFi进行时,则较少出现。这似乎有些奇怪,因为请求并没有下载很多的数据,并且要上传的文件已经在内存中(我还尝试从磁盘流式传输,但没有改善)。

我已经尝试使用NSURLConnection、Foundation CFHTTP*函数和ASIHTTPRequest库来上传文件,但无论如何,错误的发生频率都是一样的。更奇怪的是,我的所有谷歌搜索都只显示Safari有时候会出现错误代码12,我没有看到任何iOS开发人员提到过这个问题。我正在使用一个继承的代码库,所以有可能出现问题,但我甚至不知道该寻找什么。非常感谢任何见解!


什么调用会返回ENOMEM? - Jason Coco
当我使用NSURLConnection时,在didFailWithError委托方法中会出现错误--一个带有POSIX错误域、错误代码12和本地化描述"无法分配内存"的NSError。当我使用CFHTTPMessageRef时,在调用CFReadStreamRead()时失败(函数返回-1),之后errno将等于12。 - Alex Michaud
我在使用Google Docs API上传文件时,也遇到了同样的问题,尤其是在3G网络下。Dropbox API的一个帖子提到了类似的问题:http://forums.dropbox.com/topic.php?id=25351。这似乎发生在使用慢速连接(3G)上传大文件时。 - Kamchatka
1
似乎这是一个必须向苹果报告的错误。 - Kamchatka
是的,这可能是一个合法的错误。我会考虑报告它。 - Alex Michaud
3个回答

阿里云服务器只需要99元/年,新老用户同享,点击查看详情
2

我只能通过直接使用套接字并手动构造HTTP头来解决这个问题。因此,我的上传代码目前看起来像这样:

- (void)socketClose
{
    [_inputStream setDelegate:nil];
    [_inputStream close];
    [_inputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    SCR_RELEASE_SAFELY(_inputStream);

    [_outputStream setDelegate:nil];
    [_outputStream close];
    [_outputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    SCR_RELEASE_SAFELY(_outputStream);

    SCR_RELEASE_SAFELY(_headerBuffer);
}

- (void)sendRequest
{
    [self socketClose];
    SCR_RELEASE_SAFELY(_headerBuffer);

    if (!_shouldCancel)
    {
        NSString *httpMessage = [NSString stringWithFormat:@"POST upload.php HTTP/1.1\r\n"
                                 "Host:"
#ifndef TESTBED
                                 " %@"
#endif
                                 "\r\n"
                                 "User-Agent: MyApp/3.0.0 CFNetwork/534 Darwin/10.7.0\r\n"
                                 "Content-Length: %d\r\n"
                                 "Accept: */*\r\n"
                                 "Accept-Language: en-us\r\n"
                                 "Accept-Encoding: gzip, deflate\r\n"
                                 "Content-Type: application/x-www-form-urlencoded\r\n"
                                 "Connection: keep-alive\r\n\r\n"
                                 "data="
#ifndef TESTBED
                                 , [self.serverUrl host]
#endif
                                 , _bytesToUpload];

        NSString *key = @"data=";
        NSData *keyData = [key dataUsingEncoding:NSASCIIStringEncoding];
        _bytesToUpload -= [keyData length];
        _bytesToUpload = MAX(0, _bytesToUpload);

        _headerBuffer = [[NSMutableData alloc] initWithData:[httpMessage dataUsingEncoding:NSUTF8StringEncoding]];

        _writtenDataBytes = 0;

        CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault
                                           , (CFStringRef)[self.serverUrl host]
#ifdef TESTBED
                                           , 8888
#else
                                           , 80
#endif
                                           , (CFReadStreamRef *)(&_inputStream)
                                           , (CFWriteStreamRef *)(&_outputStream));

        [_inputStream setDelegate:self];
        [_outputStream setDelegate:self];

        [_inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
        [_outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];

        [_inputStream open];
        [_outputStream open];
    }
}

- (void)stream:(NSStream *)theStream handleEvent:(NSStreamEvent)streamEvent
{
    if (_outputStream == theStream)
    {
        switch (streamEvent)
        {
            case NSStreamEventOpenCompleted:
            {
                [self regenerateTimeoutTimer];
                break;
            }
            case NSStreamEventHasSpaceAvailable:
            {
                SCR_RELEASE_TIMER(_timeoutTimer);
                NSInteger length = _headerBuffer.length;

                if (length > 0)
                {
                    NSInteger written = [_outputStream write:(const uint8_t *)[_headerBuffer bytes] maxLength:length];
                    NSInteger rest = length - written;

                    if (rest > 0)
                    {
                        memmove([_headerBuffer mutableBytes], (const uint8_t *)[_headerBuffer mutableBytes] + written, rest);
                    }

                    [_headerBuffer setLength:rest];
                }
                else
                {
                    const uint8_t *dataBytes = [_data bytes];

                    while ([_outputStream hasSpaceAvailable] && (_writtenDataBytes < _bytesToUpload))
                    {
                        NSInteger written = [_outputStream write:dataBytes
                                                       maxLength:MIN(_dataLength, _bytesToUpload - _writtenDataBytes)];

                        if (written > 0)
                        {
                            _writtenDataBytes += written;
                        }
                    }
                }

                [self regenerateTimeoutTimer];

                break;
            }
            case NSStreamEventErrorOccurred:
            {
                SCR_RELEASE_TIMER(_timeoutTimer);
                [self reportError:[theStream streamError]];                
                break;
            }
            case NSStreamEventEndEncountered:
            {
                SCR_RELEASE_TIMER(_timeoutTimer);
                [self socketClose];
                break;
            }
        }
    }
    else if (_inputStream == theStream)
    {
        switch (streamEvent)
        {
            case NSStreamEventHasBytesAvailable:
            {
                SCR_RELEASE_TIMER(_timeoutTimer);

                /* Read server response here if you wish */

                [self socketClose];

                break;
            }
            case NSStreamEventErrorOccurred:
            {
                SCR_RELEASE_TIMER(_timeoutTimer);
                [self reportError:[theStream streamError]];
                break;
            }
            case NSStreamEventEndEncountered:
            {
                SCR_RELEASE_TIMER(_timeoutTimer);
                [self socketClose];
                break;
            }
        }
    }
}

虽然 ASIHTTPRequest 可以在这里使用,但我们决定放弃这些依赖,以获得更好的性能并确保一切都在我们准确的控制之下。您可以使用 Wireshark 工具来调试此类问题。


你的 regenerateTimeoutTimer 函数是做什么用的?在调用 [self stream:theStream handleEvent:streamEvent] 之前需要做些什么? - JOM
regenerateTimeoutTimer 简单地重置了我用来确定请求超时的计时器。这里的意思是,如果我们收到了一些字节,我们就会重新开始等待。但是,如果在指定的时间内(您希望的时间 - 可配置)我们不再收到任何字节,我们的类将生成一个自定义的超时异常,因此我们不会永远等待(有时它会发生在网络错误上,所以把它作为解决方法)。 - Aleks N.
在更多时间的工作中,我看到了“无法分配内存”的错误。我发现,在某些情况下,即使在上面的示例代码中也可能出现这种情况。关键是要正确打开套接字。在此处显示:https://developer.apple.com/library/ios/#samplecode/SimpleURLConnections/Introduction/Intro.html#//apple_ref/doc/uid/DTS40009245 - Aleks N.

1
解决这个问题的关键是使用流上传文件。当使用NSMutableURLRequest时,可以使用类似以下代码的方式实现:
NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPBodyStream:[NSInputStream inputStreamWithFileAtPath:filePath]];
使用ASIHTTPRequest时,可以通过以下方式流式传输文件:
ASIHTTPRequest* request = [ASIHTTPRequest requestWithURL:url];
[request setPostBodyFilePath:filePath];
rRequest.shouldStreamPostDataFromDisk = YES;

不要忘记将请求方法设置为POST或PUT。 - Thomas M
我实际上尝试过这个方法。无论如何,似乎这是处理大文件上传的正确方式,并且它可能减少了ENOMEM错误的频率,但它们仍然会发生。不过,感谢您指出这一点,这是一个好技巧。 - Alex Michaud

1

使用请求操作(NSMutableUrlConnection)时,通过在主函数中使用@autorelease{}解决了此错误。 NSPOXIS 仅偶尔出现。

- (void)main
 NSURLConnection* connection;
    @autoreleasepool //urgently needed for 3G upload
    {

        self.currentRequest = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:@"test.php"]];
        [self.currentRequest setHTTPMethod:@"PUT"];

        [self.currentRequest setHTTPBody:self.data];//inpustStream doesn't work

        connection = [NSURLConnection connectionWithRequest:self.currentRequest delegate:self];
        [connection start];

    }//end autorelease pool

        do 
        {

            [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode beforeDate: [NSDate distantFuture]];
            if ([self isCancelled])
            {
                connection          = nil;
                isFailed = YES;
                break;
            }
            self.status(statusUpdateMessage);
        } 
        while (!isFailed && !isCompleted);
        [timer invalidate];//test
        timer = nil;

//corresponding of status via blocks
        self.completed(!isFailed);
        self.status(isFailed ? errorMessage : @"Completed");
        if (isFailed)
        {
            self.failed(errorMessage != nil ? errorMessage : @"Undefined error");
        }

        self.data = nil;
        self.currentRequest = nil;

        connection = nil;

}

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