Author: thebeing
Date: Wed Jul 27 01:01:11 2016
New Revision: 40035

URL: http://svn.gna.org/viewcvs/gnustep?rev=40035&view=rev
Log:
Add [NSData initWithBytesNoCopy:length:deallocator:]

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

Modified:
    libs/base/trunk/ChangeLog
    libs/base/trunk/Headers/Foundation/NSData.h
    libs/base/trunk/Source/NSData.m
    libs/base/trunk/Tests/base/NSData/general.m

Modified: libs/base/trunk/ChangeLog
URL: 
http://svn.gna.org/viewcvs/gnustep/libs/base/trunk/ChangeLog?rev=40035&r1=40034&r2=40035&view=diff
==============================================================================
--- libs/base/trunk/ChangeLog   (original)
+++ libs/base/trunk/ChangeLog   Wed Jul 27 01:01:11 2016
@@ -1,3 +1,12 @@
+2016-07-26  Niels Grewe <[email protected]>
+
+       * 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 <[email protected]>
 
        * Source/GSStream.m:

Modified: libs/base/trunk/Headers/Foundation/NSData.h
URL: 
http://svn.gna.org/viewcvs/gnustep/libs/base/trunk/Headers/Foundation/NSData.h?rev=40035&r1=40034&r2=40035&view=diff
==============================================================================
--- libs/base/trunk/Headers/Foundation/NSData.h (original)
+++ libs/base/trunk/Headers/Foundation/NSData.h Wed Jul 27 01:01:11 2016
@@ -3,24 +3,24 @@
 
    Written by:  Andrew Kachites McCallum <[email protected]>
    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 @@
 @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 @@
 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
@@ -66,6 +67,10 @@
 /* The original name for this was NSAtomicWrite ... need for backward comapat
  */
 #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>
@@ -88,11 +93,20 @@
 + (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 @@
 #endif
 - (id) initWithData: (NSData*)data;
 
-// Accessing Data 
+// Accessing Data
 
 - (const void*) bytes;
 - (NSString*) description;
@@ -122,11 +136,11 @@
 - (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 @@
                   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

Modified: libs/base/trunk/Source/NSData.m
URL: 
http://svn.gna.org/viewcvs/gnustep/libs/base/trunk/Source/NSData.m?rev=40035&r1=40034&r2=40035&view=diff
==============================================================================
--- libs/base/trunk/Source/NSData.m     (original)
+++ libs/base/trunk/Source/NSData.m     Wed Jul 27 01:01:11 2016
@@ -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 @@
 @interface     NSDataMalloc : NSDataStatic
 @end
 
+@interface NSDataWithDeallocatorBlock : NSDataMalloc
+{
+  @private
+  GSDataDeallocatorBlock _deallocator;
+}
+@end
+
 @interface     NSMutableDataMalloc : NSMutableData
 {
   NSUInteger   length;
@@ -400,6 +411,13 @@
 }
 /* Increase capacity to at least the specified minimum value.  */
 - (void) _grow: (NSUInteger)minimum;
+@end
+
+@interface NSMutableDataWithDeallocatorBlock : NSMutableDataMalloc
+{
+  @private
+  GSDataDeallocatorBlock _deallocator;
+}
 @end
 
 #ifdef HAVE_MMAP
@@ -445,7 +463,9 @@
       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 @@
   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 @@
   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);
@@ -3311,6 +3370,38 @@
   return size;
 }
 
+@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
 
 
@@ -3609,6 +3700,35 @@
   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 @@
 
 @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

Modified: libs/base/trunk/Tests/base/NSData/general.m
URL: 
http://svn.gna.org/viewcvs/gnustep/libs/base/trunk/Tests/base/NSData/general.m?rev=40035&r1=40034&r2=40035&view=diff
==============================================================================
--- libs/base/trunk/Tests/base/NSData/general.m (original)
+++ libs/base/trunk/Tests/base/NSData/general.m Wed Jul 27 01:01:11 2016
@@ -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 @@
        [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;
 }


_______________________________________________
Gnustep-cvs mailing list
[email protected]
https://mail.gna.org/listinfo/gnustep-cvs

Reply via email to