NSPOSIXErrorDomain Code=12 "Cannot allocate memory" in 3G network - iphone

I'm trying to send a file on FTP server in my iPhone application.
Everything seems to be okay in WiFi and GSM:EDGE network, but in 3G network an error appears (not always, but very often):
Error Domain=NSPOSIXErrorDomain
Code=12 "The operation couldn’t be
completed. Cannot allocate memory"
Below the code where the error appears:
- (void)stream:(NSStream*)aStream handleEvent:(NSStreamEvent)eventCode {
switch( eventCode ) {
case NSStreamEventHasSpaceAvailable: {
if( _readDataOffset == _readDataLimit ) {
NSInteger readDataLen = [_readStream read:[_readData mutableBytes] maxLength:kReadDataLength];
NSLog(#"readDataLen is %d",readDataLen);
if( -1 == readDataLen ) {
_error = [[_readStream streamError] retain];
_keepRunning = NO;
} else if( 0 == readDataLen ) {
_keepRunning = NO;
} else {
_readDataOffset = 0;
_readDataLimit = readDataLen;
}
}
if( _readDataOffset != _readDataLimit ) {
NSOutputStream* writeStream = (NSOutputStream*)aStream;
uint8_t *buffer = (void *)[_readData bytes];
// vvvv and here the value of writtenDataLen is often -1 (but only on 3G network)
NSInteger writtenDataLen = [writeStream write:&buffer[_readDataOffset] maxLength:_readDataLimit - _readDataOffset];
if( writtenDataLen > 0 ) {
_readDataOffset += writtenDataLen;
_writtenDataLen += writtenDataLen;
[self ftpPutDidWriteInternal];
} else if( -1 == writtenDataLen ) {
_error = [[writeStream streamError] retain];
_keepRunning = NO;
}
}
} break;
case NSStreamEventErrorOccurred: {
_error = [aStream.streamError retain];
_keepRunning = NO;
} break;
}
}
What can be important, the whole sending is done in separate thread which has it's own NSAutoreleasePool.
Is there anyone who got the issue? Any suggestion? I would be appreciate.
UPDATE:
I've just checked that popular iPhone application "FTP On The Go" has got the same (?) issue during sending a file in 3G network! There is no error handled, but the transfer stops.
UPDATE 2:
I can't believe it, but it's true: SimpleFTPSample from Apple is also affected with the issue.

And here it is - the solution (or rather workaround):
you should set property of writeStream to false, to switch off default persistant connection
CFWriteStreamSetProperty( (CFWriteStreamRef)writeStreamRef, kCFStreamPropertyFTPAttemptPersistentConnection, kCFBooleanFalse ) ;

Have resolved this error with using operation for request (NSMutableUrlConnection) with #autorelease{} for main function
- (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;
}

Related

OSAtomicIncrement32Barrier deprecated, how to solve this issue?

i'm trying Parse server on my ios app using Xcode14 and ios 16, I installed the pod Parse, but when I run the code I get the following warning message:
'OSAtomicIncrement32Barrier' is deprecated: first deprecated in iOS 10.0 - Use atomic_fetch_add() from <stdatomic.h> instead
Any help how I could fix this issue:
+ (instancetype)taskForCompletionOfAllTasks:(nullable NSArray<BFTask *> *)tasks {
__block int32_t total = (int32_t)tasks.count;
if (total == 0) {
return [self taskWithResult:nil];
}
__block int32_t cancelled = 0;
NSObject *lock = [[NSObject alloc] init];
NSMutableArray *errors = [NSMutableArray array];
BFTaskCompletionSource *tcs = [BFTaskCompletionSource taskCompletionSource];
for (BFTask *task in tasks) {
[task continueWithBlock:^id(BFTask *t) {
if (t.error) {
#synchronized (lock) {
[errors addObject:t.error];
}
} else if (t.cancelled) {
OSAtomicIncrement32Barrier(&cancelled); // error is here
}
if (OSAtomicDecrement32Barrier(&total) == 0) { // error is here
if (errors.count > 0) {
if (errors.count == 1) {
tcs.error = [errors firstObject];
} else {
NSError *error = [NSError errorWithDomain:BFTaskErrorDomain
code:kBFMultipleErrorsError
userInfo:#{ BFTaskMultipleErrorsUserInfoKey: errors }];
tcs.error = error;
}
} else if (cancelled > 0) {
[tcs cancel];
} else {
tcs.result = nil;
}
}
return nil;
}];
}
return tcs.task;
}
C11 has support for atomics so you can do something like:
#include <stdatomic.h>
// Declare an atomic int and initialize it to zero
atomic_int total;
atomic_init(&total, 0);
// Note: C17 and C23 allow safe direct initialization
// i.e. atomic_int total = 0;
// Operations such as ++total and --total are atomic
++total;
// Or alternatively
atomic_fetch_add(&total, 1);
atomic_fetch_sub(&total, 1);

iOS GameCenter Matchmaker not working

I’m trying to make a custom matchmakingview using a matchmaker. The code below is used to find a match.
When i run this on two different devices with different Game Center accounts, both will get a match but none will connect to the match. They will just get stuck in the while loop in infinity and never get out. Have i missed something, do you need to call something to actually connect to the match?
- (void) findMatch{
GKMatchRequest *request = [[GKMatchRequest alloc] init];
request.minPlayers = 2;
request.maxPlayers = 2;
request.playersToInvite = nil;
NSLog(#"Start searching!");
[matchmaker findMatchForRequest:request
withCompletionHandler:^(GKMatch *match, NSError *error)
{
if (error) {
// Print the error
NSLog(#"%#", error.localizedDescription);
}
else if (match != nil)
{
curMatch = match;
curMatch.delegate = self;
NSLog(#"Expected: %i", match.expectedPlayerCount);
while (match.expectedPlayerCount != 0){
NSLog(#"PLayers: %i", curMatch.playerIDs.count);
}
NSLog(#"Start match!");
}
}];
You should not be using a while loop to wait for expectedPlayerCount to reach 0, instead implement the GKMatchDelegate method:
- (void)match:(GKMatch *)match player:(NSString *)playerID didChangeState:(GKPlayerConnectionState)state {
if (!self.matchStarted && match.expectedPlayerCount == 0) {
self.matchStarted = YES;
//Now you should start your match.
}
}

A memory leak happen when doing ftp request in the background thread

I am developing an app which can download image from the server through ftp request and then display them. The ftp request is running in the background. When the download finished, a update message will be sent to the view.
I encountered a memory leak when doing the ftp request which has the following features:
1. The memory leak do not happen every time. May be 1 / 7.
2. If I do the ftp request on the main thread, everything is OK.
3. If I do the ftp request on simulator, everything is OK.
I used SIMPLEFTP to do the ftp job and I have done some modification to fix my request.
In the FtpListService.m, This file is used to request a document list information (name, size, modification date). Memory leak is happened here (I highlight the line with "####").
//This is the method to start a ftp request
- (void)_startReceive
// Starts a connection to download the current URL.
{
BOOL success;
NSURL * url;
CFReadStreamRef ftpStream;
//don't tap receive twice in a row!
assert(self.networkStream == nil);
// First get and check the URL.
self.InputUrl = [self.InputUrl stringByAddingPercentEscapesUsingEncoding:NSASCIIStringEncoding];
url = [FtpUtil smartURLForString:self.InputUrl];
success = (url != nil);
// If the URL is bogus, let the user know. Otherwise kick off the connection.
if (!success) {
DLog(#"Bad ftp url.");
} else {
// Create the mutable data into which we will receive the listing.
assert(self.listData != nil);
// Open a CFFTPStream for the URL.
ftpStream = CFReadStreamCreateWithFTPURL(NULL, (CFURLRef) url);
assert(ftpStream != NULL);
self.networkStream = (NSInputStream *) ftpStream;
self.networkStream.delegate = self;
[self.networkStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:RUNLOOPMODEL];
//This timer will be called to terminate the request which is blocked for a customed time.
NSTimer* timer = [NSTimer scheduledTimerWithTimeInterval:TIMEOUTFTPLIST target:self
selector:#selector(listdealTimeOut:) userInfo:nil repeats:NO];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:RUNLOOPMODEL];
[self.networkStream open];
CFRelease(ftpStream);
}
}
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
// An NSStream delegate callback that's called when events happen on our
// network stream.
{
connected = #"connected";
switch (eventCode) { //####################### EXC_BAD_ACCESS
case NSStreamEventOpenCompleted: {
//NSLog(#"NSStreamEventOpenCompleted");
} break;
case NSStreamEventHasBytesAvailable: {
NSInteger bytesRead;
uint8_t buffer[LISTDOCBUFFER];
// Pull some data off the network.
bytesRead = [self.networkStream read:buffer maxLength:sizeof(buffer)];
if (bytesRead == -1) {
[self _stopReceiveWithStatus:#"Network read error"];
} else if (bytesRead == 0) {
[self _stopReceiveWithStatus:#"no more data"];
} else {
assert(self.listData != nil);
// Append the data to our listing buffer.
[self.listData appendBytes:buffer length:bytesRead];
[self _parseListData];
}
} break;
case NSStreamEventHasSpaceAvailable: {
//NSLog(#"NSStreamEventHasSpaceAvailable");
assert(NO); // should never happen for the output stream
} break;
case NSStreamEventErrorOccurred: {
DLog(#"NSStreamEventErrorOccurred");
[self _stopReceiveWithStatus:#"Stream open error"];
} break;
case NSStreamEventEndEncountered: {
DLog(#"NSStreamEventEndEncountered");
// ignore
} break;
default: {
DLog(#"default");
assert(NO);
} break;
}
}
In the FtpService.m. Here I counld specify the address and trytime to do ftp request:
- (NSArray *)requstServerListInfo:(NSString *)filename tryTime:(int)tryTime
{
NSArray *result = nil;
//Create the request ftp path
NSString* tm = [NSString stringWithFormat:FTPURL];
if(filename != nil)
tm = [NSString stringWithFormat:#"%#%#/",FTPURL,filename];
while (tryTime-- > 0) {
FtpListService *listService = [[FtpListService alloc] initWithUrl:tm];
[listService _startReceive];
//isReceiving will be NO only when : connect error, time out, correctly done job
//I do not really understand the loop, I just know this will cause the request job to begin
while (listService.isReceiving) {
[[NSRunLoop currentRunLoop] runMode:RUNLOOPMODEL beforeDate:[NSDate distantFuture]];
}
//if correctly request, dirArray != nil
if(listService.dirArray == nil) {
[listService release];
continue;
} else {
result = listService.dirArray;
[listService release];
break;
}
}
return result;
}
The ftp job start from PGNetConductor.m which is a singleton:
pm = [[PGDataManagement alloc] init];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
result = [pm startNetWork];
}
PGDataManagement is owned by PGNetConductor : #property (nonatomic, unsafe_unretained) PGDataManagement *pm;
I try a lot but failed to solve the problem. Hope someone could give me some advice. If you need the code or more infomation , tell me. Thanks!

How to handle internet connection interruption on iOS using CFNetwork?

I have implemented an FTP client on the iPhone, but when the connection is interrupted for a moment while download from the FTP server is in progress, the application informs me there is no connection and stops the client.
Here comes the problem: next time i try to start the download process again, the event stream:handleEvent: is not fired and the streamStatus of the networkStream stays on NSStreamStatusOpening.
If I manually stop the download process (using the same method which I fire when connection is interrupted), I can then re-download again. I have to relaunch the whole application for the downloading to work again.
Here are the key parts of the code:
- (void)downloadFile:(NSDictionary *)file {
NSURL *url;
CFReadStreamRef ftpStream;
url = [NSURL URLWithString:[#"PATH TO FTP FILE" stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
[self.fileStream open];
ftpStream = CFReadStreamCreateWithFTPURL(NULL, (__bridge CFURLRef) url);
self.networkStream = (__bridge NSInputStream *) ftpStream;
self.networkStream.delegate = self;
[self.networkStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[self.networkStream open];
CFRelease(ftpStream);
}
and the method that is fired when something happens with the stream
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode {
switch (eventCode) {
case NSStreamEventOpenCompleted: {
[self updateStatus:#"Opened connection"];
} break;
case NSStreamEventHasBytesAvailable: {
NSInteger bytesRead;
uint8_t buffer[32768];
bytesRead = [self.networkStream read:buffer maxLength:sizeof(buffer)];
if (bytesRead == -1) {
[self stopReceivingWithStatus:#"Network read error"];
} else if (bytesRead == 0) {
[self stopReceivingWithStatus:nil];
} else {
[self processStreamDataWithBuffer:buffer andReadBytes:bytesRead];
}
} break;
case NSStreamEventHasSpaceAvailable: {
assert(NO);
} break;
case NSStreamEventErrorOccurred: {
[self stopReceivingWithStatus:#"Stream open error"];
} break;
case NSStreamEventEndEncountered: {
} break;
default: {
assert(NO);
} break;
}
The problem as I said is that after connection interrupted, the events are not fired anymore.
Note: I am using iOS 5 with ARC.
Perhaps you should set the flag to force the stream to close the underlying native socket which by default is not done;
[self.networkStream setProperty:(id)kCFBooleanTrue forKey:(NSString *)kCFStreamPropertyShouldCloseNativeSocket];

CFStream IOS Socket communication

how can i clear an CFStream buffer?
Everytime i read from socket there is still data from an old request, i mean complete response to an old request not just a fragment of it.
Am i missing something ?
This is a function i use to initialize the connection:
-(void)openSocketConnection:(UInt32)port: (NSString *)host
{
NSString *hoststring = [[NSString alloc] initWithString:host];
CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault,(__bridge CFStringRef)hoststring ,
port,&_nnet_readStream,&_nnet_writeStream);
CFWriteStreamCanAcceptBytes(_nnet_writeStream);
CFWriteStreamSetProperty(_nnet_writeStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
CFReadStreamSetProperty(_nnet_readStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
if(!CFWriteStreamOpen(_nnet_writeStream)) {
NSLog(#"Error Opening Write Socket");
}
if(!CFReadStreamOpen(_nnet_readStream)) {
NSLog(#"Error Opening Write Socket");
}
}
This is a function i use to read data from socket:
BOOL done = NO;
NSMutableString* result = [NSMutableString string];
while (!done)
{
if (CFReadStreamHasBytesAvailable(_nnet_readStream))
{
UInt8 buf[1024];
CFIndex bytesRead = CFReadStreamRead(_nnet_readStream, buf, 1024);
if (bytesRead < 0)
{
CFStreamError error = CFReadStreamGetError(_nnet_readStream);
NSLog(#"%#",error);
} else if (bytesRead == 0)
{
if (CFReadStreamGetStatus(_nnet_readStream) == kCFStreamStatusAtEnd)
{
done = YES;
}
} else
{
[result appendString:[[NSString alloc] initWithBytes:buf length:bytesRead encoding:NSUTF8StringEncoding]];
}
} else
{
done = YES;
}
}
You seem to assume that when you get data from the host on the other end it will always be available all in one go.
I don't know what the underlying protocol is, but in general it's entirely possible that the remote end will try and send 1600 bytes and that you'll get 1500 bytes but then have to wait a few milliseconds (or even seconds) for the next 100 bytes. Meanwhile CFReadStreamHasBytesAvailable would return false and so your code would set your done flag even though you haven't read all the data the server sent.