20

If I create a new NSData object of a specific size using dataWithBytes:length:, what is the most efficient way to create the input bytes (20 Mb worth) of random characters, preferably without reading the data in from a file? I need a unique buffer of a specific size each time.

Thanks

Bama91
  • 884
  • 2
  • 10
  • 24

9 Answers9

25

You can create a 20*2^20b NSData object, then append a random 4 byte integer to it 20*2^20/4 times with arc4random(). I believe you need to include stdlib.h (via Generating random numbers in Objective-C).

#include <stdlib.h>

-(NSData*)create20mbRandomNSData
{
  int twentyMb           = 20971520;
  NSMutableData* theData = [NSMutableData dataWithCapacity:twentyMb];
  for( unsigned int i = 0 ; i < twentyMb/4 ; ++i )
  { 
    u_int32_t randomBits = arc4random();
    [theData appendBytes:(void*)&randomBits length:4];
  }
  return theData;
}
Community
  • 1
  • 1
Thomson Comer
  • 3,919
  • 3
  • 30
  • 32
22
void * bytes = malloc(numberOfBytes);
NSData * data = [NSData dataWithBytes:bytes length:numberOfBytes];
free(bytes);

The bytes are not 'random', but will contain garbage values (whatever was on the heap before this was run). The advantage being its fast and the code is concise.

Robert
  • 37,670
  • 37
  • 171
  • 213
  • 3
    It is worth noting that you should be a little careful about where you use this data. For example, it would not be a good idea to subsequently send this data to a server - as it would basically be providing a dump of your memory. – James Billingham May 13 '14 at 12:46
  • @JamesBillingham - Good point! Its got heartbleed written all over it :) I was actually assuming this was for testing something. – Robert May 13 '14 at 14:04
  • 2
    This will not always work as if you ask for too many bytes, malloc() will directly go to the kernel and allocate pages, which are zero'ed out by default. Furthermore if you run this snippet very soon after your app launched, malloc with return memory that has never been used before (i.e. from newly allocated pages), so also zero'ed out. – Pol Sep 13 '15 at 02:22
  • 1
    This is horrendous, it does not generate/return random bytes, it returns bytes that are anything but random, either zero or previously used bytes that have a pattern! – zaph May 12 '16 at 15:16
  • 1
    NSData *data = [[NSData alloc] initWithBytesNoCopy:bytes length: numberOfBytes freeWhenDone:YES]; – Roman Solodyashkin Oct 27 '16 at 14:17
16

Here's a 3-liner swift version:

Swift 2

let length = 2048
let bytes = [UInt32](count: length, repeatedValue: 0).map { _ in arc4random() }
let data = NSData(bytes: bytes, length: bytes.count * sizeof(UInt32))

Swift 3

let bytes = [UInt32](repeating: 0, count: length).map { _ in arc4random() }
let data = Data(bytes: bytes, count: length)
ldiqual
  • 15,015
  • 6
  • 52
  • 90
  • 4
    in Swift 3 it's `let bytes = [UInt32](repeating: 0, count: length).map { _ in arc4random() } let data = Data(bytes: bytes, count: length )`. maybe you want to update your comment – user2378197 Dec 01 '16 at 16:25
  • Thanks, this is perfect! – Baran Jun 08 '18 at 09:28
8

You might consider using CCRandomGenerateBytes function from CommonCrypto to generate random data. Like:

func generateBytes(length : Int) throws -> NSData? {
    var bytes = [UInt8](count: length, repeatedValue: UInt8(0))
    let statusCode = CCRandomGenerateBytes(&bytes, bytes.count)
    if statusCode != CCRNGStatus(kCCSuccess) {
        return nil
    }
    return NSData(bytes: bytes, length: bytes.count)
}
sgl0v
  • 1,357
  • 11
  • 13
6

Swift 3:

import Security

func randomBytes(length: Int) -> Data {
    var data = Data(capacity: length)
    data.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<UInt8>) -> Void in
        let _ = SecRandomCopyBytes(kSecRandomDefault, length, bytes)
    }
    return data
}

Swift 5 Update:

func randomBytes(length: Int) -> Data? {
    var data = Data(count: length)
    let result = data.withUnsafeMutableBytes {
        SecRandomCopyBytes(kSecRandomDefault, length, $0.baseAddress!)
    }
    guard result == errSecSuccess else {
        return nil
    }
    return data
}

Thanks to this post.

garafajon
  • 1,278
  • 13
  • 15
4

Use arc4random_buf to fill the buffer with random bytes

Obj-C

+ (nullable NSData *)radomDataOfSize:(size_t)sizeInBytes
{
    void *buff = malloc(sizeInBytes);
    if (buff == NULL) {
        return nil;
    }
    arc4random_buf(buff, sizeInBytes);

    return [NSData dataWithBytesNoCopy:buff length:sizeInBytes freeWhenDone:YES];
}
lgarbo
  • 750
  • 10
  • 12
2

The original version has a bug but mine takes care of that and hopefully doesn't introduce any new one. Hope it helps.

- (NSData *)randomDataWithBytes: (NSUInteger)length {
    NSMutableData *mutableData = [NSMutableData dataWithCapacity: length];
    for (unsigned int i = 0; i < size; i++) { 
        NSInteger randomBits = arc4random();
        [mutableData appendBytes: (void *) &randomBits length: 1];
    } return mutableData;
}

Here is its unit test:

NSInteger givenLength = INT16_MAX;
NSData *randomData = [self randomDataWithBytes: givenLength];
STAssertTrue([randomData length] == givenLength,
             @"RandomDataWithBytes Failed Expected size %d and got %d", 
             givenLength, [randomData length]);
Constantino Tsarouhas
  • 6,846
  • 6
  • 43
  • 54
0

I've open sourced my JFRandom class over at github which can do exactly this. Here's a blog post demonstrating how to obtain/use it to achieve your goal...

http://jayfuerstenberg.com/devblog/generating-random-numbers-strings-and-data-in-objective-c

Jason Fuerstenberg
  • 1,341
  • 13
  • 13
0

urandom is more efficient.

Here is a category to generate random buffers:

@interface NSMutableData(Random)
+(id)randomDataWithLength:(NSUInteger)length;
@end

@implementation NSMutableData(Random)
+(id)randomDataWithLength:(NSUInteger)length
{
    NSMutableData* data=[NSMutableData dataWithLength:length];
    [[NSInputStream inputStreamWithFileAtPath:@"/dev/urandom"] read:(uint8_t*)[data mutableBytes] maxLength:length];
    return data;
}
@end
Gilad Novik
  • 4,546
  • 4
  • 41
  • 58