Question

I am new in Apple developing (about 3 months) and now using Xcode 4.5.1 with ARC of all my projects. I was trying display an mjpeg video in my app (also, ARC). As mjpeg coding is based on independent jpeg images, I used NSImageWell to display every frame of mjpeg flow. However, when I switched frame, I noticed the memory leak.

The .h file:

#import <Cocoa/Cocoa.h>

@interface AMCImagiaTestViewController : NSViewController
- (void) startVideo;
- (void) stopVideo;
@end

The .m file:

#import "AMCImagiaTestViewController.h"
#include "AMCCommonLib.h"   // My UNIX-like system call library. Used in the thread to call socket functions. Don't mind.
#import "TTImage.h"   // This is NSImage. I only add an NSLog() in -dealloc method

/* forward declaration for a POSIX thread which receives jpeg data.
 This thread receives jpeg images sent by a process of a Linux PC. 
 The socket content is simple enough: standard jpeg data started with 
 FFD8 and end with FFD9 */
void *threadReceiveFrameData(void *arg);

@interface AMCImagiaTestViewController ()
@property (nonatomic, assign) IBOutlet NSImageView *imageVideoFrame;  // the outlet to the image in nib file
@property (nonatomic, assign) pthread_t hdlThread;
/* here are some properties used in thread but I can close or release if I kill it outside its thread loop */
@property (nonatomic, assign) int sockFd;
@property (nonatomic, assign) BOOL isThreadRunning;
@property (nonatomic, retain) NSData *dataForImage;
@property (nonatomic, retain) TTImage *tempImage;
@end

@implementation AMCImagiaTestViewController
...   // some non-important methods

- (void) startVideo
{
    [self stopVideo];
    /* Start the receiving thread */    
    pthread_create(&_hdlThread, NULL, threadReceiveFrameData, (__bridge void*)self);
}

- (void) stopVideo
{
    if (_isThreadRunning)
    {
        pthread_cancel(_hdlThread);
        usleep(100000);
        pthread_join(_hdlThread, NULL);
        _hdlThread = 0;
        if (_sockFd)
        {
            simpleSocketClose(_sockFd);
            _sockFd = 0;
        }
    }
}

...
@end  // end of @implementation AMCImagiaTestViewController


// Now comes to the POSIX thread
void *threadReceiveFrameData(void *arg)
{
    @autoreleasepool {
        AMCImagiaTestViewController *ctrl;
        struct sockaddr_in sockMessage;
        uint8_t buff[60001];
        ssize_t dataLen;

        memset(buff, 0, sizeof(buff));
        ctrl = (__bridge AMCImagiaTestViewController*) arg;

        pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
        ctrl.isThreadRunning = YES;

        /* socket(). create a UDP socket */
        ctrl.sockFd = simpleSocketCreate_udp();
        if (!ctrl.sockFd)
        {
            SYS_PERROR(); // a package of perror(). don't mind
            goto THREAD_EXIT;
        }

        /* bind(). 22222 is the local port */       
        if (simpleSocketBind_udp(ctrl.sockFd, 22222) < 0)
        {
            SYS_PERROR();
            goto THREAD_EXIT;
        }
        NSLog(@"pthread init OK");

        do
        {
            /* my package of recvfrom(). 
             The first NULL means receive from any IP.
             The second NULL means never timeout (using the select() system call) */
            dataLen = simpleSocketReceiveFrom_udp(ctrl.sockFd, NULL, &sockMessage, buff, sizeof(buff) - 1, NULL);
            if (dataLen > 0)
            {
                //NSLog(@"received: %ld", dataLen);
                ctrl.dataForImage = nil;
                ctrl.dataForImage = [NSData dataWithBytes:buff
                                                   length:dataLen];

                ctrl.tempImage = nil;
                ctrl.tempImage = [[TTImage alloc] initWithData:ctrl.dataForImage];

                /* MEMORY LEAK??? */
                /* These two lines causes memory leaks.
                 I would explain it outside this code block */
                [ctrl.imageVideoFrame setImage:nil];
                [ctrl.imageVideoFrame setImage: ctrl.tempImage];

            }
            else
            {
                SYS_PERROR();
            }
        }
        while (dataLen > 0);




    THREAD_EXIT:
        NSLog(@"pthread exit");
        ctrl.isThreadRunning = NO;
        if (ctrl.sockFd)
        {
            simpleSocketClose(ctrl.sockFd);
            ctrl.sockFd = 0;
        }
        pthread_exit(NULL);
    }
}

Explanation of the "MEMORY LEAK???" in the code block:
This line set the NSImageWell to display the image. If I continued transmitting jpeg data, I noticed that the memory of this app kept going up and would not go significantly down. I add the "= nil" trying to create an [.. release] method but this did not work. However, if I comment out these two lines, no memory leaks noticed any more (and my video function not achieved either...)

Any suggestions? Or I should switch to non-ARC mode to examine this problem? Thanks very much!!

Newer study On Jan, 25:
I create another simple project without ARC

NSImage is still subclassed as TTImage (TT means TesT) as:
.m file:

@implementation TTImage
- (id)retain{
    NSLog(@"%08x retain", (unsigned int)self);
    return [super retain];
}
- (oneway void)release{
    NSLog(@"release");
    return [super release];
}
- (void)dealloc{
    NSLog(@"%@ deallocated.", self);
    return [super dealloc];
}
@end

The pthread is now defined in the App delegate file:

   ...   // AppDelegate implementations

#define AMCRelease(obj) if(obj) {[(obj) release]; (obj) = nil;}

void *threadReceiveFrameData(void *arg)
{
    @autoreleasepool
    {
        AMCAppDelegate *ctrl;
        NSImage *lastImage;
        struct sockaddr_in sockAddr;
        uint8_t buff[60001];
        ssize_t dataLen;
        NSData *dataInThread = nil;
        TTImage *imageInThread = nil;

        ctrl = (AMCAppDelegate*)arg;
        memset (buff, 0, sizeof(buff));

        ...
        // the same initialization as before

        NSLog(@"pthread init.");

        do {
            dataLen = simpleSocketReceiveFrom_udp(ctrl.sockFd, NULL, &sockAddr, buff, sizeof(buff) - 1, NULL);
            if (dataLen > 0)
            {
                printf("\n");
                AMCRelease(dataInThread);
                dataInThread = [NSData dataWithBytes:buff length:dataLen];
                NSLog(@"%d data rc: %ld", __LINE__, [dataInThread retainCount]);  // Line 100

                if (imageInThread)
                {
                    NSLog(@"%d image pre rc: %ld", __LINE__, [imageInThread retainCount]);  // Line 104
                    //lastImage = imageInThread;
                }               
                AMCRelease(imageInThread);

                imageInThread = [[TTImage alloc] initWithData:dataInThread];
                NSLog(@"%d data rc: %ld", __LINE__, [dataInThread retainCount]);  // Line 110
                NSLog(@"%d image rc: %ld", __LINE__, [imageInThread retainCount]);  // Line 111

                //[ctrl.imageVideoFrame setImage:nil];
                [ctrl.imageVideoFrame setImage:imageInThread];
                NSLog(@"%d image rc: %ld", __LINE__, [imageInThread retainCount]); // Line 115
                //NSLog(@"image last rc: %ld", [lastImage retainCount]);
            }
            else
            {
                SYS_PERROR();
            }
        } while (dataLen > 0);


    THREAD_EXIT:
        AMCRelease(dataInThread);
        AMCRelease(imageInThread);
        if (ctrl.sockFd)
        {
            simpleSocketClose(ctrl.sockFd);
            ctrl.sockFd = 0;
        }
        pthread_exit(NULL);
    }
}

And then I send several jpegs to the app. Here are the outputs (headers of every line ignored):

pthread init.
100 data rc: 1 
110 data rc: 2 
111 image rc: 1 
0067d890 retain    <-- looks OK
115 image rc: 2

100 data rc: 1 
104 image pre rc: 2
release 
110 data rc: 2 
111 image rc: 1 
0067d890 retain   <-- What???  Leaks???
0067d890 retain   <-- What???
00632ba0 retain   <-- looks OK
115 image rc: 2
100 data rc: 1 
104 image pre rc: 2
release 
110 data rc: 2 
111 image rc: 1 
00632ba0 retain  <-- What???
00632ba0 retain  <-- What???
0069d580 retain 
115 image rc: 2
Was it helpful?

Solution

OK, I thing I may solved my problem. Just add @autoreleasepool inside the do-while block of the ARC project. Like:

    do {
        @autoreleasepool{
            dataLen = ...

            ... // previous codes need no change

            else
            {
                SYS_PERROR();
            }
        } // release pool ends
    } while (dataLen > 0);

However, I still do not know why about my later study of non-ARC one.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top