1

I have some code using NSPurgeableData that was working, until my machine's memory usage started running high, at which point I started to get an exception thrown. This is the message in the exception:

*** Terminating app due to uncaught exception 'NSGenericException', reason: '*** -[NSPurgeableData length]: accessed outside successful -beginContentAccess and -endContentAccess calls.'

This is the essence of my code:

- (NSImage *)imageProperty {
    if (!self.purgeableImage || ![self.purgeableImage beginContentAccess]) {
        if (!self.purgeableImage) {
            self.purgeableImage = [NSPurgeableData data];
        }

        NSImage *image = // Get the image

        // THIS LINE CAUSES THE EXCEPTION
        [self.purgeableImage setData: image.TIFFRepresentation];
    }
}

- (void)clearImage {
    [self.purgeableImage endContentAccess];

    // Putting in this line works, but shouldn't be necessary, should it?
    //self.purgeableImage = nil
}

clearImage is balanced with calls to imageProperty. I'm able to reproduce the exception with this test case:

- (void)testImageProperty_CallTwiceWithMemoryPressure {
    MyClass *instance = // Initialize the sucker

    NSImage *image = instance.imageProperty;
    NSPurgeableData *purgeableData = image.purgeableImage;

    XCTAssertNotNil(image, @"Image not loaded");
    XCTAssertEqual(image.size.width, (CGFloat)988, @"Image loaded with incorrect width");
    XCTAssertEqual(image.size.height, (CGFloat)1500, @"Image loaded with incorrect height");

    [instance clearImage];

    // Discard, as if there were memory pressure
    [purgeableData discardContentIfPossible];
    XCTAssertTrue(purgeableData.isContentDiscarded, @"Failed to simulate memory pressure");

    // TRIGGERS THE EXCEPTION
    image = instance.imageProperty;
    XCTAssertNotNil(image, @"Image not loaded on second attempt");
}

I can eliminate the exception by uncommenting the second line in -clearImage, but I didn't think this was necessary. Shouldn't I be able to continue using an NSPurgeableData object after its original data has been discarded?

Update: Additional workaround

The problem with nilling it out after calling -endContentAccess is that the class then effectively provides no caching. Another solution I found is to update the leading if statements like so:

    if (!self.purgeableImage || self.purgeableImage.isContentDiscarded || ![self.purgeableImage beginContentAccess]) {
        if (!self.purgeableImage || self.purgeableImage.isContentDiscarded) {
Dov
  • 15,530
  • 13
  • 76
  • 177

0 Answers0