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) {