Source/NSData+TNLAdditions.m (96 lines of code) (raw):

// // NSData+TNLAdditions.m // TwitterNetworkLayer // // Created on 9/9/16. // Copyright © 2020 Twitter. All rights reserved. // #import <objc/runtime.h> #import "NSData+TNLAdditions.h" NS_ASSUME_NONNULL_BEGIN @implementation NSData (TNLAdditions) - (NSData *)tnl_safeSubdataNoCopyWithRange:(NSRange)subRange { if (subRange.location == 0 && subRange.length == self.length) { // exact match, return early return self; } if (subRange.location + subRange.length > self.length) { // out of range, throw exception just like [NSData subdataWithRange:] NSString *subrangeString = NSStringFromRange(subRange); NSString *rangeString = NSStringFromRange(NSMakeRange(0, self.length)); @throw [NSException exceptionWithName:NSRangeException reason:[NSString stringWithFormat:@"subdata %@ is out of range %@!", subrangeString, rangeString] userInfo:nil]; } if (subRange.length == 0) { // zero length is still valid return [NSData data]; } #if __LP64__ __block dispatch_data_t dispatchData = dispatch_data_create("", 0, NULL, NULL); #else NSMutableData *mutableData = [[NSMutableData alloc] init]; #endif [self enumerateByteRangesUsingBlock:^(const void * _Nonnull bytes, NSRange byteRange, BOOL * _Nonnull stop) { if (byteRange.location >= (subRange.location + subRange.length)) { // past the end *stop = YES; return; } if ((byteRange.location + byteRange.length) <= subRange.location) { // before the beginning return; } NSRange cutRange = byteRange; NSInteger delta; delta = (NSInteger)subRange.location - (NSInteger)cutRange.location; if (delta > 0) { // byteRange provided offers excess bytes at the beginning, disregard those cutRange.length -= (NSUInteger)delta; cutRange.location = subRange.location; } delta = (NSInteger)(cutRange.location + cutRange.length) - (NSInteger)(subRange.location + subRange.length); if (delta > 0) { // byteRange provided offers excess bytes at the end, disregard those cutRange.length -= (NSUInteger)delta; } // find byte pointer, which is bytes plus our calculated offset for start of bytes const void *bytePtr = bytes + (cutRange.location - byteRange.location); // append the data as-is (no copy, no free when done) #if __LP64__ dispatch_data_t cutData = dispatch_data_create(bytePtr, cutRange.length, NULL, ^{ /*noop*/ }); dispatchData = dispatch_data_create_concat(dispatchData, cutData); #else // 32 bit NSData *rData = [NSData dataWithBytesNoCopy:(void*)bytePtr length:cutRange.length freeWhenDone:NO]; [mutableData appendData:rData]; #endif }]; NSData *retData = nil; #if __LP64__ retData = (NSData *)dispatchData; // nice! #else // 32 bit retData = (NSData *)mutableData; #endif // preserve the source data for the lifetime of the subdata objc_setAssociatedObject(retData, _cmd, self, OBJC_ASSOCIATION_RETAIN_NONATOMIC); return retData; } - (nullable NSData *)tnl_safeSubdataNoCopyWithRange:(NSRange)subRange error:(out NSError * __nullable * __nullable)outError { NSData *data = nil; @try { data = [self tnl_safeSubdataNoCopyWithRange:subRange]; } @catch (NSException *e) { // convert the exception to an error (then return nil) if (outError) { *outError = [NSError errorWithDomain:NSPOSIXErrorDomain code:[e.name isEqualToString:NSRangeException] ? ERANGE : EBADMSG userInfo:@{ @"exception" : e }]; } } return data; } - (NSString *)tnl_hexStringValue { static const unsigned char hexLookup[] = "0123456789abcdef"; const NSUInteger hexLength = self.length * 2; if (!hexLength) { return @""; } unichar* hexChars = (unichar*)malloc(sizeof(unichar) * (hexLength)); __block unichar *hexCharPtr = hexChars; [self enumerateByteRangesUsingBlock:^(const void *bytes, NSRange byteRange, BOOL *stop) { unsigned char *bytePtr = (unsigned char *)bytes; for (NSUInteger i = 0; i < byteRange.length; ++i) { const unsigned char byte = *bytePtr++; *hexCharPtr++ = hexLookup[(byte >> 4) & 0xF]; *hexCharPtr++ = hexLookup[byte & 0xF]; } }]; return [[NSString alloc] initWithCharactersNoCopy:hexChars length:hexLength freeWhenDone:YES]; } @end NS_ASSUME_NONNULL_END