0

I'm trying to send files over a bluetooth connection.

I've got this to work thanks to my previous post, but the method wasn't memory efficient.

The whole file was loaded into memory before it was sent, and this created problems (and crashed the app) for files > ~20 MB. So I've come up with a new method of only reading parts of the file I need at a specific time, creating packets from the data, sending them and repeating the process for each 8KB chunk of the file.

So I made a class method that generates the packet, informs the controller that a packet is available (through a protocol) and repeats this process for each packet that's available.

Here's the code for the packet generator:

+ (void)makePacketsFromFile:(NSString *)path withDelegate:(id <filePacketDelegate>)aDelegate {
    if (![[NSFileManager defaultManager] fileExistsAtPath:path] || aDelegate == nil) return;

    id <filePacketDelegate> delegate;
    delegate = aDelegate;

    const NSUInteger quanta = 8192;
    uint filesize;

    NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:nil];
    filesize = [[fileAttributes objectForKey:NSFileSize] intValue];

    int numOfPackets = (int)ceil(filesize/quanta);
    if (numOfPackets == 0) numOfPackets = 1;

    NSLog(@"filesize = %d, numOfPackets = %d or %.3f", filesize, numOfPackets, (float)ceil(filesize/quanta));

    NSFileHandle *handle = [NSFileHandle fileHandleForReadingAtPath:path];

    int offset = 0;
    int counter = 0;

    while (counter != (numOfPackets + 1)) {
        uint len = (filesize < quanta) ? filesize : quanta;
        if (counter == numOfPackets) {
            len = filesize - offset;
        }
        [handle seekToFileOffset:offset];
        NSData *fileData = [handle readDataOfLength:len];
        file_packet *packet = [[file_packet alloc] initWithFileName:[path lastPathComponent] ofType:0 index:counter];

        packet.packetContents = fileData;
        [fileData release];
        packet.checksum = @"<to be done>";
        packet.numberOfPackets = [NSString stringWithFormat:@"%d", numOfPackets];

        [delegate packetIsReadyForSending:packet];

        [packet release];

        offset += quanta;
        counter++;
    }

    [handle closeFile];

}  

And receiving and sending the file:

- (void)packetIsReadyForSending:(file_packet *)packet {
    NSData *fileData = [packet dataForSending];
    [self.connectionSession sendDataToAllPeers:fileData withDataMode:GKSendDataReliable error:nil];
}

- (void)sendFileViaBluetooth {
    [file_packet makePacketsFromFile:selectedFilePath withDelegate:self];
}

However, the memory use is quite large. Not what I expected.

I'm a bit stuck on this, as I wouldn't like to restrict bluetooth sharing to files smaller than 20MB.

Any help appreciated.

Edit:
I've been thinking about this for a while and have come to the conclusion that it's not my code that's causing the memory allocation issue, it's GameKit's stack for sending the packets.

I think I'm generating too many packets too fast, and I don't think GameKit is sending them quick enough.

So I am now thinking about a way to see when GameKit has sent a packet, and only generating another one once GameKit confirms it was sent.

Community
  • 1
  • 1
Jack Greenhill
  • 10,240
  • 12
  • 38
  • 70

2 Answers2

2

There’s one clear memory management issue in your code:

NSData *fileData = [handle readDataOfLength:len];

fileData wasn’t obtained via NARC (a method whose name contains new, alloc, retain, copy), so you don’t own it, hence you don’t release it.

[fileData release];

Oops.

As for the memory use, one thing to consider is that even though you release packet you can’t really tell what’s going on inside -[GKSession sendDataToAllPeers:withDataMode:error:]. It is possible that the method internally creates autoreleased objects that only end up being released after +makePacketsFromFile:withDelegate: has finished executing. I suggest you use a new autorelease pool in every loop iteration:

while (counter != (numOfPackets + 1)) {
    NSAutoreleasePool *pool = [NSAutoreleasePool new];

    uint len = (filesize < quanta) ? filesize : quanta;
    …
    counter++;

    [pool drain];
}

By doing this, any autoreleased object inside that loop will be effectively released at the end of each loop iteration.

  • Sadly, memory usage is sky-high. I'm not sure what's allocating all of this memory, this is becoming a bit annoying. – Jack Greenhill Apr 24 '11 at 09:09
  • @Jack Do you release your instance variables/set your declared properties to `nil` in `-[file_packet dealloc]`? –  Apr 24 '11 at 09:12
0

Checkout the NSInputStream. With it you can open a stream and only read out a buffer of bytes at a time.

Wayne Hartman
  • 18,369
  • 7
  • 84
  • 116