Objective C and ARC: Object produced in one thread

2019-08-19 02:53发布

问题:

I have a small project that reads an HTTP stream from a remote server, demuxes it, extracts audio stream, decodes it into 16-bit PCM, and feeds into a corresponding AudioQueue. The decoder/demuxer/fetcher runs in a separate thread and it uses my home-grown blocking queue (see code below) to deliver the decoded frames to the AudioQueue callback. The queue uses NSMutableArray to store objects.

Once this thing is in-flight, it leaks objects inserted into the queue. Memory profiler says that the RefCt is 2 by the time I expect it to be 0 and to be released by ARC.

Here are the queue/dequeue methods:

- (id) dequeue {
    dispatch_semaphore_wait(objectsReady, DISPATCH_TIME_FOREVER);
    [lock lock];
    id anObject = [queue objectAtIndex:0];
    [queue removeObjectAtIndex:0];
    [lock unlock];
    dispatch_semaphore_signal(freeSlots);

    return anObject;
}

- (void) enqueue:(id)element {
    dispatch_semaphore_wait(freeSlots, DISPATCH_TIME_FOREVER);
    [lock lock];
    [queue addObject:element];
    [lock unlock];
    dispatch_semaphore_signal(objectsReady);
}

Producer thread does this:

[pAudioFrameQueue enqueue:[self convertAVFrameAudioToPcm:audioFrame]];

And the "convertAVFrameAudioToPcm" methods looks like this:

- (NSData*) convertAVFrameAudioToPcm:(AVFrame*)frame {
    NSData* ret = nil;
    int16_t* outputBuffer = malloc(outputByteLen);
    // decode into outputBuffer and other stuff
    ret = [NSData dataWithBytes:outputBuffer length:outputByteLen];
    free(outputBuffer);

    return ret;
}

Consumer does this:

- (void) fillAvailableAppleAudioBuffer:(AudioQueueBufferRef)bufferToFill {
    @autoreleasepool {
        NSData* nextAudioBuffer = [pAudioFrameQueue dequeue];
        if (nextAudioBuffer != nil) {
            [nextAudioBuffer getBytes:bufferToFill->mAudioData]; // I know this is not safe
            bufferToFill->mAudioDataByteSize = nextAudioBuffer.length;
        } else {
            NSLog(@"ERR: End of stream...");
        }
    }
}

To me it looks like RefCt should become 0 when fillAvailableAppleAudioBuffer exits, but apparently ARC disagrees and does not release the object.

Am I having a bug in my simple queue code?

Or do I instantiate NSData in a wrong way?

Or am I missing some special rule of how ARC works between threads? By the way, the producer threads starts like this:

- (BOOL) startFrameFetcher {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
                                             (unsigned long)NULL),
                   ^(void) {
                       [self frameFetcherThread];
                    });
    return YES;
}

Any hints will be much appreciated!

PS: and last but not the least, I do have another instance of the same blocking queue that stores video frames that I dequeue and show via NSTimer. Video frames do not leak! I am guessing this may have something to do with threading. Otherwise, I would have expected to see the leak in both queues.