【问题标题】:POSIX error 12 ("Cannot allocate memory") while uploading files from an iPhone从 iPhone 上传文件时出现 POSIX 错误 12(“无法分配内存”)
【发布时间】:2010-12-27 23:00:55
【问题描述】:

我正在开发一个 iPhone 应用程序,该应用程序涉及将相机中的完整照片(通常在 1.5 到 2.0 MB 之间)及其缩略图(小得多)上传到 Amazon S3。

缩略图总是会成功上传,但有时完整的图像不会,当它们失败时,它们会失败并显示 POSIX 错误代码 12,即 ENOMEM。但是,我添加了调试代码来打印错误发生时的可用内存量,并且总是有相当多的可用内存,通常超过 100 MB。

此外,当通过 3G 进行上传时,错误出现的频率更高,而通过 wifi 上传时出现的频率更少——这看起来很奇怪,因为请求没有下载太多并且正在上传的文件已经在内存中(I'我还尝试过从磁盘流式传输它,但没有任何改进)。

我尝试使用 NSURLConnection、Foundation CFHTTP* 函数和 ASIHTTPRequest 库上传文件,但无论如何,错误发生的频率相同。更奇怪的是,我的谷歌搜索显示最终用户有时会从 Safari 获得错误代码 12——我还没有看到任何 iOS 开发人员提到它。我正在使用继承的代码库,所以它可能有问题,但我什至不确定要寻找什么。任何见解将不胜感激!

【问题讨论】:

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

标签: iphone http ios put


【解决方案1】:

我能够解决此问题的唯一方法是直接使用套接字并手动形成 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] 并在此之前执行...?
  • regenerateTimeoutTimer 只是重置我用来确定请求何时超时的计时器。这里的意思是,如果我们得到了一些字节,我们又开始等待了。但是如果我们在指定的时间内没有收到更多字节(只要您愿意 - 可配置),我们的类会生成一个自定义超时异常,因此我们不会永远等待(有时它会发生在网络错误上所以作为一种解决方法)。
  • 经过一段时间的工作并看到“无法分配内存”错误,我发现在某些情况下它甚至可能出现在上面的示例代码中。关键是正确打开插座。显示在这里:developer.apple.com/library/ios/#samplecode/…
【解决方案2】:

解决此问题的关键是使用流上传文件。使用 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。
  • 我确实试过这个。无论如何,这似乎是大文件上传的方法,它可能降低了 ENOMEM 错误的频率,但它们仍然会发生。不过,感谢您指出这一点,这是一项很好的技术。
【解决方案3】:

已通过使用请求操作 (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;

}

【讨论】:

    猜你喜欢
    • 2018-03-29
    • 2010-11-16
    • 1970-01-01
    • 1970-01-01
    • 2011-07-14
    • 2017-06-14
    • 2020-07-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多