A memory leak happen when doing ftp request in the

2019-09-17 16:42发布

问题:

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!