Add [NSData initWithBytesNoCopy:length:deallocator:]

This new initializer allows customising the deallocation behaviour
through user-supplied blocks. 


git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/base/trunk@40035 72102866-910b-0410-8b05-ffd578937521
This commit is contained in:
Niels Grewe 2016-07-26 23:01:11 +00:00
parent 525d467e18
commit c2be055c3e
4 changed files with 332 additions and 36 deletions

View file

@ -1,3 +1,12 @@
2016-07-26 Niels Grewe <niels.grewe@halbordnung.de>
* Headers/Foundation/NSData.h
* Source/NSData.m
* Tests/base/NSData/general.m:
Implement OS X 10.9 method -initWithBytesNoCopy:length:deallocator:
that allows customising data deallocation based on a caller
supplied block. Also adds test cases for this functionality.
2016-07-26 Richard Frith-Macdonald <rfm@gnu.org>
* Source/GSStream.m:

View file

@ -3,24 +3,24 @@
Written by: Andrew Kachites McCallum <mccallum@gnu.ai.mit.edu>
Date: 1995
This file is part of the GNUstep Base Library.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free
Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02111 USA.
*/
*/
#ifndef __NSData_h_GNUSTEP_BASE_INCLUDE
#define __NSData_h_GNUSTEP_BASE_INCLUDE
@ -29,6 +29,7 @@
#import <Foundation/NSObject.h>
#import <Foundation/NSRange.h>
#import <Foundation/NSSerialization.h>
#import <GNUstepBase/GSBlocks.h>
#if defined(__cplusplus)
extern "C" {
@ -39,7 +40,7 @@ extern "C" {
@class NSURL;
#endif
#if OS_API_VERSION(MAC_OS_X_VERSION_10_9,GS_API_LATEST)
#if OS_API_VERSION(MAC_OS_X_VERSION_10_9,GS_API_LATEST)
enum {
NSDataBase64DecodingIgnoreUnknownCharacters = (1UL << 0)
};
@ -54,7 +55,7 @@ enum {
typedef NSUInteger NSDataBase64EncodingOptions;
#endif
#if OS_API_VERSION(MAC_OS_X_VERSION_10_4,GS_API_LATEST)
#if OS_API_VERSION(MAC_OS_X_VERSION_10_4,GS_API_LATEST)
enum {
NSMappedRead = 1,
NSUncachedRead = 2
@ -68,6 +69,10 @@ enum {
#define NSAtomicWrite NSDataWritingAtomic
#endif
#if OS_API_VERSION(MAC_OS_X_VERSION_10_9,GS_API_LATEST)
DEFINE_BLOCK_TYPE(GSDataDeallocatorBlock, void, void*, NSUInteger);
#endif
@interface NSData : NSObject <NSCoding, NSCopying, NSMutableCopying>
// Allocating and Initializing a Data Object
@ -88,11 +93,20 @@ enum {
+ (id) dataWithContentsOfURL: (NSURL*)url;
#endif
+ (id) dataWithData: (NSData*)data;
#if OS_API_VERSION(MAC_OS_X_VERSION_10_9,GS_API_LATEST)
#if OS_API_VERSION(MAC_OS_X_VERSION_10_9,GS_API_LATEST)
- (id) initWithBase64EncodedData: (NSData*)base64Data
options: (NSDataBase64DecodingOptions)options;
- (id) initWithBase64EncodedString: (NSString*)base64String
options: (NSDataBase64DecodingOptions)options;
/**
* <override-subclass/>
* Initialize the receiver to hold memory pointed to by bytes without copying.
* When the receiver is deallocated, the memory will be freed using the user
* supplied deallocator block.
*/
- (instancetype) initWithBytesNoCopy: (void*)bytes
length: (NSUInteger)length
deallocator: (GSDataDeallocatorBlock)deallocator;
#endif
- (id) initWithBytes: (const void*)aBuffer
length: (NSUInteger)bufferSize;
@ -110,7 +124,7 @@ enum {
#endif
- (id) initWithData: (NSData*)data;
// Accessing Data
// Accessing Data
- (const void*) bytes;
- (NSString*) description;
@ -122,11 +136,11 @@ enum {
- (NSData*) subdataWithRange: (NSRange)aRange;
// base64
#if OS_API_VERSION(MAC_OS_X_VERSION_10_9,GS_API_LATEST)
#if OS_API_VERSION(MAC_OS_X_VERSION_10_9,GS_API_LATEST)
- (NSData *) base64EncodedDataWithOptions: (NSDataBase64EncodingOptions)options;
- (NSString *) base64EncodedStringWithOptions: (NSDataBase64EncodingOptions)options;
#endif
// Querying a Data Object
- (BOOL) isEqualToData: (NSData*)other;
@ -171,7 +185,7 @@ enum {
count: (unsigned int)numInts
atIndex: (unsigned int)index;
#if OS_API_VERSION(MAC_OS_X_VERSION_10_4,GS_API_LATEST)
#if OS_API_VERSION(MAC_OS_X_VERSION_10_4,GS_API_LATEST)
/**
* <p>Writes a copy of the data encapsulated by the receiver to a file
* at path. If the NSDataWritingAtomic option is set, this writes to a

View file

@ -52,10 +52,12 @@
* NSDataMappedFile Memory mapped files.
* NSDataShared Extension for shared memory.
* NSDataFinalized For GC of non-GC data.
* NSDataWithDeallocatorBlock Adds custom deallocation behaviour
* NSMutableData Abstract base class.
* NSMutableDataMalloc Concrete class.
* NSMutableDataShared Extension for shared memory.
* NSDataMutableFinalized For GC of non-GC data.
* NSMutableDataWithDeallocatorBlock Adds custom deallocation behaviour
*
* NSMutableDataMalloc MUST share it's initial instance variable layout
* with NSDataMalloc so that it can use the 'behavior' code to inherit
@ -126,6 +128,8 @@
static Class dataStatic;
static Class dataMalloc;
static Class mutableDataMalloc;
static Class dataBlock;
static Class mutableDataBlock;
static Class NSDataAbstract;
static Class NSMutableDataAbstract;
static SEL appendSel;
@ -390,6 +394,13 @@ failure:
@interface NSDataMalloc : NSDataStatic
@end
@interface NSDataWithDeallocatorBlock : NSDataMalloc
{
@private
GSDataDeallocatorBlock _deallocator;
}
@end
@interface NSMutableDataMalloc : NSMutableData
{
NSUInteger length;
@ -402,6 +413,13 @@ failure:
- (void) _grow: (NSUInteger)minimum;
@end
@interface NSMutableDataWithDeallocatorBlock : NSMutableDataMalloc
{
@private
GSDataDeallocatorBlock _deallocator;
}
@end
#ifdef HAVE_MMAP
@interface NSDataMappedFile : NSDataMalloc
@end
@ -445,7 +463,9 @@ failure:
NSMutableDataAbstract = [NSMutableData class];
dataStatic = [NSDataStatic class];
dataMalloc = [NSDataMalloc class];
dataBlock = [NSDataWithDeallocatorBlock class];
mutableDataMalloc = [NSMutableDataMalloc class];
mutableDataBlock = [NSMutableDataWithDeallocatorBlock class];
appendSel = @selector(appendBytes:length:);
appendImp = [mutableDataMalloc instanceMethodForSelector: appendSel];
}
@ -824,6 +844,14 @@ failure:
return nil;
}
- (instancetype) initWithBytesNoCopy: (void*)bytes
length: (NSUInteger)length
deallocator: (GSDataDeallocatorBlock)deallocator
{
[self subclassResponsibility: _cmd];
return nil;
}
/**
* Initialises the receiver with the contents of the specified file.<br />
* Returns the resulting object.<br />
@ -3300,6 +3328,37 @@ getBytes(void* dst, void* src, unsigned len, unsigned limit, unsigned *pos)
return self;
}
- (instancetype) initWithBytesNoCopy: (void*)buf
length: (NSUInteger)len
deallocator: (GSDataDeallocatorBlock)deallocator
{
if (buf == NULL && len > 0)
{
[self release];
[NSException raise: NSInvalidArgumentException
format: @"[%@-initWithBytesNoCopy:length:deallocator:] called with "
@"length but NULL bytes", NSStringFromClass([self class])];
}
else if (NULL == deallocator)
{
// For a nil deallocator we can just swizzle into a static data object
GSClassSwizzle(self, dataStatic);
bytes = buf;
length = len;
return self;
}
/* This is a bit unfortunate: Our implementation has no space to hold the
* deallocator block ivar in NSDataMalloc, so if we are invoked via this
* initialiser, we have to undo the previous allocation and reallocate
* ourselves as NSDataWithDeallocatorBlock.
*/
[self release];
return [[dataBlock alloc] initWithBytesNoCopy: buf
length: len
deallocator: deallocator];
}
- (NSUInteger) sizeInBytesExcluding: (NSHashTable*)exclude
{
NSUInteger size = GSPrivateMemorySize(self, exclude);
@ -3313,6 +3372,38 @@ getBytes(void* dst, void* src, unsigned len, unsigned limit, unsigned *pos)
@end
@implementation NSDataWithDeallocatorBlock
- (instancetype) initWithBytesNoCopy: (void*)buf
length: (NSUInteger)len
deallocator: (GSDataDeallocatorBlock)deallocator
{
if (buf == NULL && len > 0)
{
[self release];
[NSException raise: NSInvalidArgumentException
format: @"[%@-initWithBytesNoCopy:length:deallocator:] called with "
@"length but NULL bytes", NSStringFromClass([self class])];
}
bytes = buf;
length = len;
ASSIGN(_deallocator, deallocator);
return self;
}
- (void) dealloc
{
if (_deallocator != NULL)
{
CALL_BLOCK(_deallocator, bytes, length);
}
// Clear out the ivars so that super doesn't double free.
bytes = NULL;
length = 0;
[super dealloc];
}
@end
#ifdef HAVE_MMAP
@implementation NSDataMappedFile
@ -3609,6 +3700,35 @@ getBytes(void* dst, void* src, unsigned len, unsigned limit, unsigned *pos)
return self;
}
- (instancetype) initWithBytesNoCopy: (void*)buf
length: (NSUInteger)len
deallocator: (GSDataDeallocatorBlock)deallocator
{
if (buf == NULL && len > 0)
{
[self release];
[NSException raise: NSInvalidArgumentException
format: @"[%@-initWithBytesNoCopy:length:deallocator:] called with "
@"length but NULL bytes", NSStringFromClass([self class])];
}
else if (NULL == deallocator)
{
// Can reuse this class.
return [self initWithBytesNoCopy: buf
length: len
freeWhenDone: NO];
}
/*
* Custom deallocator. Need to re-allocate as an instance of
* NSMutableDataWithDeallocatorBlock
*/
[self release];
return [[mutableDataBlock alloc] initWithBytesNoCopy: buf
length: len
deallocator: deallocator];
}
// THIS IS THE DESIGNATED INITIALISER
/**
* Initialize with buffer capable of holding size bytes.
@ -4108,6 +4228,105 @@ getBytes(void* dst, void* src, unsigned len, unsigned limit, unsigned *pos)
@end
@implementation NSMutableDataWithDeallocatorBlock
+ (id) allocWithZone: (NSZone*)z
{
return NSAllocateObject(mutableDataBlock, 0, z);
}
- (instancetype) initWithBytesNoCopy: (void*)buf
length: (NSUInteger)len
deallocator: (GSDataDeallocatorBlock)deallocator
{
if (buf == NULL && len > 0)
{
[self release];
[NSException raise: NSInvalidArgumentException
format: @"[%@-initWithBytesNoCopy:length:deallocator:] called with "
@"length but NULL bytes", NSStringFromClass([self class])];
}
/* The assumption here is that the superclass if fully concrete and will
* not return a different instance. This invariant holds for the current
* implementation of NSMutableDataMalloc, but not NSDataMalloc.
*/
if (nil == (self = [super initWithBytesNoCopy: buf
length: len
freeWhenDone: NO]))
{
return nil;
}
ASSIGN(_deallocator, deallocator);
return self;
}
- (void) dealloc
{
if (_deallocator != NULL)
{
CALL_BLOCK(_deallocator, bytes, capacity);
// Clear out the ivars so that super doesn't double free.
bytes = NULL;
length = 0;
DESTROY(_deallocator);
}
[super dealloc];
}
- (id) setCapacity: (NSUInteger)size
{
/* We need to override capacity modification so that we correctly call the
* block when we are operating on the initial allocation, usual malloc/free
* machinery otherwise. */
if (size != capacity)
{
void *tmp;
tmp = NSZoneMalloc(zone, size);
if (tmp == 0)
{
[NSException raise: NSMallocException
format: @"Unable to set data capacity to '%"PRIuPTR"'", size];
}
if (bytes)
{
memcpy(tmp, bytes, capacity < size ? capacity : size);
if (_deallocator != NULL)
{
CALL_BLOCK(_deallocator, bytes, capacity);
DESTROY(_deallocator);
zone = NSDefaultMallocZone();
}
else
{
NSZoneFree(zone, bytes);
}
}
else if (_deallocator != NULL)
{
CALL_BLOCK(_deallocator, bytes, capacity);
DESTROY(_deallocator);
zone = NSDefaultMallocZone();
}
bytes = tmp;
capacity = size;
growth = capacity/2;
if (growth == 0)
{
growth = 1;
}
}
if (size < length)
{
length = size;
}
return self;
}
@end
#ifdef HAVE_SHMCTL
@implementation NSMutableDataShared

View file

@ -5,20 +5,20 @@
#import <Foundation/NSString.h>
int main()
{
{
NSAutoreleasePool *arp = [NSAutoreleasePool new];
char *str1,*str2;
NSData *data1, *data2;
NSMutableData *mutable;
char *hold;
str1 = "Test string for data classes";
str2 = (char *) malloc(sizeof("Test string for data classes not copied"));
str2 = (char *) malloc(sizeof("Test string for data classes not copied"));
strcpy(str2,"Test string for data classes not copied");
mutable = [NSMutableData dataWithLength:100];
hold = [mutable mutableBytes];
hold = [mutable mutableBytes];
/* hmpf is this correct */
data1 = [NSData dataWithBytes:str1 length:(strlen(str1) * sizeof(void*))];
PASS(data1 != nil &&
@ -27,52 +27,106 @@ int main()
[data1 bytes] != str1 &&
strcmp(str1,[data1 bytes]) == 0,
"+dataWithBytes:length: works");
data2 = [NSData dataWithBytesNoCopy:str2 length:(strlen(str2) * sizeof(void*))];
PASS(data2 != nil && [data2 isKindOfClass:[NSData class]] &&
[data2 length] == (strlen(str2) * sizeof(void*)) &&
[data2 bytes] == str2,
"+dataWithBytesNoCopy:length: works");
data1 = [NSData dataWithBytes:nil length:0];
PASS(data1 != nil && [data1 isKindOfClass:[NSData class]] &&
[data1 length] == 0,
PASS(data1 != nil && [data1 isKindOfClass:[NSData class]] &&
[data1 length] == 0,
"+dataWithBytes:length works with 0 length");
[data2 getBytes:hold range:NSMakeRange(2,6)];
[data2 getBytes:hold range:NSMakeRange(2,6)];
PASS(strcmp(hold,"st str") == 0, "-getBytes:range works");
PASS_EXCEPTION([data2 getBytes:hold
PASS_EXCEPTION([data2 getBytes:hold
range:NSMakeRange(strlen(str2)*sizeof(void*),1)];,
NSRangeException,
"getBytes:range: with bad location");
PASS_EXCEPTION([data2 getBytes:hold
PASS_EXCEPTION([data2 getBytes:hold
range:NSMakeRange(1,(strlen(str2)*sizeof(void*)))];,
NSRangeException,
"getBytes:range: with bad length");
PASS_EXCEPTION([data2 subdataWithRange:NSMakeRange((strlen(str2)*sizeof(void*)),1)];,
NSRangeException,
"-subdataWithRange: with bad location");
PASS_EXCEPTION([data2 subdataWithRange:NSMakeRange(1,(strlen(str2)*sizeof(void*)))];,
NSRangeException,
"-subdataWithRange: with bad length");
data2 = [NSData dataWithBytesNoCopy:str1
data2 = [NSData dataWithBytesNoCopy:str1
length:(strlen(str1) * sizeof(void*))
freeWhenDone:NO];
PASS(data2 != nil && [data2 isKindOfClass:[NSData class]] &&
[data2 length] == (strlen(str1) * sizeof(void*)) &&
[data2 bytes] == str1,
[data2 bytes] == str1,
"+dataWithBytesNoCopy:length:freeWhenDone: works");
[arp release]; arp = nil;
{
{
BOOL didNotSegfault = YES;
PASS(didNotSegfault, "+dataWithBytesNoCopy:length:freeWhenDone:NO doesn't free memory");
}
START_SET("deallocator blocks")
# ifndef __has_feature
# define __has_feature(x) 0
# endif
# if __has_feature(blocks)
uint8_t stackBuf[4] = { 1, 2, 3, 5 };
__block NSUInteger called = 0;
NSData *immutable =
[[NSData alloc] initWithBytesNoCopy: stackBuf
length: 4
deallocator: ^(void* bytes, NSUInteger length) {
called++;
}];
PASS_RUNS([immutable release]; immutable = nil;,
"No free() error with custom deallocator");
PASS(called == 1, "Deallocator block called");
uint8_t *buf = malloc(4 * sizeof(uint8_t));
NSMutableData *mutable =
[[NSMutableData alloc] initWithBytesNoCopy: buf
length: 4
deallocator: ^(void *bytes, NSUInteger len)
{
free(bytes);
called++;
}
];
PASS_RUNS([mutable release]; mutable = nil;,
"No free() error with custom deallocator on mutable data");
PASS(called == 2, "Deallocator block called on -dealloc of mutable data");
buf = malloc(4 * sizeof(uint8_t));
mutable =
[[NSMutableData alloc] initWithBytesNoCopy: buf
length: 4
deallocator: ^(void *bytes, NSUInteger len)
{
free(bytes);
called++;
}
];
PASS_RUNS([mutable setCapacity: 10];,
"Can set capactiy with custom deallocator on mutable data");
PASS(called == 3,
"Deallocator block called on -setCapacity: of mutable data");
PASS_RUNS([mutable release]; mutable = nil;,
"No free() error with custom deallocator on mutable data "
"after capacity change");
PASS(called == 3, "Deallocator block not called on -dealloc of mutable data "
"after its capacity has been changed");
# else
SKIP("No Blocks support in the compiler.")
# endif
END_SET("deallocator blocks")
return 0;
}