GameKit's sendDataToAllPeers isn't sending packets quick enough - iphone

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.

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.

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

Related

How to Transfer Large Files over wifi in iOS

I downloaded WiTap Code From Apple's website. Its for transferring data over local wifi network. I am working in a project to interact as client - server architecture. I am sending NSData from client side to server.
I made 2 projects; one for client and one for server
At client side project, i made following Changes
For that I modified the AppController.m file by adding following method
AppController.m (Client side)
- (void)sendData:(NSData*)pobjData
{
assert(self.streamOpenCount == 2);
if ( [self.outputStream hasSpaceAvailable] )
{
NSInteger bytesWritten;
NSUInteger length = [pobjData length];
bytesWritten = [self.outputStream write:[pobjData bytes] maxLength:[pobjData length]];
NSLog(#"written bytes -> %d",bytesWritten);
}
}
Then by calling this method I send data.
At Server side project, I made following chagnes for that I modified the AppController.m file by modifying following method
AppController.m (Server side)
- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode
{
#pragma unused(stream)
switch(eventCode) {
case NSStreamEventOpenCompleted: {
self.streamOpenCount += 1;
assert(self.streamOpenCount <= 2);
// Once both streams are open we hide the picker and the game is on.
if (self.streamOpenCount == 2) {
[self dismissPicker];
[self.server deregister];
}
} break;
case NSStreamEventHasSpaceAvailable: {
assert(stream == self.outputStream);
// do nothing
} break;
case NSStreamEventHasBytesAvailable:
{
if (stream == self.inputStream)
{
NSInteger bytesRead;
uint32_t buffer[32768];
NSMutableData *_data = [NSMutableData data];
// Pull some data off the network.
bytesRead = [self.inputStream read:buffer maxLength:sizeof(buffer)];
if (bytesRead == -1) {
} else if (bytesRead == 0) {
} else {
// FIXME: Popup an alert
const long long expectedContentLength = bytesRead;
NSUInteger expectedSize = 0;
// expectedContentLength can be represented as NSUInteger, so cast it:
expectedSize = (NSUInteger)expectedContentLength;
[_data appendBytes:buffer length:expectedSize];
NSLog(#"\"Data received has length: %d", _data.length);
[self performSelector:#selector(getData:) withObject:_data afterDelay:1.0];
}
}
}
break;
default:
assert(NO);
// fall through
case NSStreamEventErrorOccurred:
// fall through
case NSStreamEventEndEncountered: {
[self setupForNewGame];
} break;
}
}
and added a method to write the received data as a file
#define kUserDirectoryPath NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)
-(void)getData:(NSMutableData *)pData
{
NSFileManager *tmpmanager = [NSFileManager defaultManager];
[tmpmanager createFileAtPath:[AppController getDocumentDirectoryPath:[NSString stringWithFormat:#"%#.png",[NSDate date]]] contents:pData attributes:nil];
}
+(NSString*)getDocumentDirectoryPath:(NSString*)pStrPathName
{
NSString *strPath=nil;
if(pStrPathName)
strPath = [[kUserDirectoryPath objectAtIndex:0] stringByAppendingPathComponent:pStrPathName];
return strPath;
}
I convert .png files to NSData and send them from client side to server side. the server downloads the file to Document Directory
The matter is , when i transfer file from client side , it gets downloaded to server side at document directory. Everything works fine in case of tiny files. If file size exceeds to 8kB , file written at document directory gets corrupted.
Kindly help me to be able to send large files.
The problem is that your code doesn't loop to gather all of the available data till the end (or loop to send all data either). So you only ever receive the first buffer of data. If the image is small then that works ok, if the image is bigger then it never will.
You need to write the code so that it keeps sending when there is buffer space until all data is sent and keep reading data (into an NSMutableData instance variable, not a local variable) until the end of the stream is reached.
You can use AsyncSocket which can be downloaded from
https://github.com/roustem/AsyncSocket ,
this is an objective-c wrapper build on CFSocket and CFNetwork, it can handle large amount of data transfer with TCP/UDP protocol on local wifi.
You can find the wiki here https://github.com/darkseed/cocoaasyncsocket/wiki/iPhone
The class is very simple and easy to implement.Give it a try
You have make a web service from where you need to put IP address of the system, Where you want to send the file and after that when you can connected with the entered IP address you can send the file in Base64 and NSData format.

Upload video to FTP from iDevice

I am working on an APP for user to upload videos to our FTP server
So far, everything almost done but I met one issue is that after users upload videos(.MOV), I failed to open and play the files.
The error message that quicktime player returns is "can't open because the movie's file format is not recognized"
In my codes, I let users select videos by using ALAssetsLibrady
Then load the video into an ALAsset object, before start uploading, load the video into a NSInputStream object from ALAsset, here is the codes.
ALAssetRepresentation *rep = [currentAsset defaultRepresentation];
Byte *buffer = (Byte*)malloc(rep.size);
NSUInteger buffered = [rep getBytes:buffer fromOffset:0.0 length:rep.size error:nil];
NSData *data = [NSData dataWithBytesNoCopy:buffer length:buffered freeWhenDone:YES];
iStream = [NSInputStream inputStreamWithData:data];
[iStream open];
Next step is to set a NSOutputStream and open it, handle uploading operation by following codes.
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
{
switch (eventCode) {
case NSStreamEventNone:
{
break;
}
case NSStreamEventOpenCompleted:
{
//opened connection
NSLog(#"opened connection");
break;
}
case NSStreamEventHasBytesAvailable:
{
// should never happen for the output stream
[self stopSendWithStatus:#"should never happen for the output stream"];
break;
}
case NSStreamEventHasSpaceAvailable:
{
// If we don't have any data buffered, go read the next chunk of data.
NSInteger bufferSize = 65535;
uint8_t *buffer = malloc(bufferSize);
if (bufferOffset == bufferLimit) {
NSInteger bytesRead = [iStream read:buffer maxLength:bufferSize];
if (bytesRead == -1) {
[self stopSendWithStatus:#"file read error"];
} else if (bytesRead == 0) {
[self stopSendWithStatus:nil];
} else {
bufferOffset = 0;
bufferLimit = bytesRead;
}
}
// If we're not out of data completely, send the next chunk.
if (bufferOffset != bufferLimit) {
NSInteger bytesWritten = [oStream write:&buffer[bufferOffset] maxLength:bufferLimit - bufferOffset];
if (bytesWritten == -1) {
[self stopSendWithStatus:#"file write error"];
} else {
bufferOffset += bytesWritten;
}
}
//NSLog(#"available");
break;
}
case NSStreamEventErrorOccurred:
{
//stream open error
[self stopSendWithStatus:[[aStream streamError] description]];
break;
}
case NSStreamEventEndEncountered: //ignore
NSLog(#"end");
break;
}
}
There is no any error occurs, the video file does upload to FTP with correct file size and name, but just can't open it.
Anybody knows any clue?
I have made NSInputStream implementation for streaming ALAsset objects - POSInputStreamLibrary. It doesn't read the whole 1GB video into memory as your solution, but reads movie with chunks instead. Of course this is not the only feature of POSBlobInputStream. More info at my GitHub repository.
I know this probably isn't the answer you're looking for, but you should NOT use a direct connection via FTP to allow users to upload files to your webserver. It's unsecure and slow compared with REST.
Instead, why not write a tiny bit of php to handle the upload, and POST the file from the app via REST? here:
$uploaddir = 'uploads/';
$file = basename($_FILES['file']['name']);
$uploadfile = $uploaddir . $file;
I also recommend using AFNetworking to handle the POST request http://afnetworking.com/
First of all,I guess you meant to reduce memory capacity by convert ALAsset to NSInputStream other than NSData.But you convert it to NSData firstly then convert NSData you got to NSInputStream,it doesn't make sense and would not reduce memory capacity for you have already put your video into memory with NSData.
So if you want to transfer your video via Stream in order to reduce memory pressure(or you have no choice because your video is up to 2GB or more),you should use CFStreamCreateBoundPair to upload file chunk by chunk,see the Apple iOS Developer Library written below.
For large blocks of constructed data, call CFStreamCreateBoundPair to create a pair of streams, then call the setHTTPBodyStream: method to tell NSMutableURLRequest to use one of those streams as the source for its body content. By writing into the other stream, you can send the data a piece at a time.
I have a swift version of converting ALAsset to NSInputStream via CFStreamCreateBoundPair in github.The key point is just like the Documents written.Another reference is this question.
Hope it would be helpful for you.

stxxl and overriding cxx_constructor

I'm trying to use stxxl as a backing store to a vector. If I do nothing it will automatically allocate 1 gig of disk space and everything works perfectly. However I don't need that much space allocated and, in fact, I need to gracefully scale from my optimal 512 megs down to a min of 128 megs of storage.
Unfortunately I have stxxl::vector defined in my objective-c class and on instantiation of that class the cxx_constructor function is called which starts up stxxl and allocated a gig whether i like it or not.
Is there any way I can override the cxx_constructor call and add my init in before it goes on to instantiate my class? I did try and create a simple object that would get instantiated by cxx_constructor. Unfortunately, however, for some unknown reason cxx_constructor calls my class's constructor twice.
Is the only option to add a static to that class preventing it from getting instantiated more than once? This is definitely an option just not very elegant. For one I'd love to know why it gets called twice.
Any info much appreciated!
Edit: Here is the code I wrote.
namespace stxxl
{
class Config
{
float GetFreeDiskspace()
{
float totalSpace = 0.0f;
float totalFreeSpace = 0.0f;
NSError *error = nil;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSDictionary *dictionary = [[NSFileManager defaultManager] attributesOfFileSystemForPath:[paths lastObject] error: &error];
if (dictionary) {
NSNumber *fileSystemSizeInBytes = [dictionary objectForKey: NSFileSystemSize];
NSNumber *freeFileSystemSizeInBytes = [dictionary objectForKey:NSFileSystemFreeSize];
totalSpace = [fileSystemSizeInBytes floatValue];
totalFreeSpace = [freeFileSystemSizeInBytes floatValue];
NSLog(#"Memory Capacity of %f MiB with %f MiB Free memory available.", ((totalSpace/1024.0f)/1024.0f), ((totalFreeSpace/1024.0f)/1024.0f));
} else {
NSLog(#"Error Obtaining System Memory Info: Domain = %#, Code = %#", [error domain], [error code]);
}
return totalFreeSpace;
}
public:
Config()
{
char cacheFileName[256];
NSString* pTempDir = NSTemporaryDirectory();
strcpy( cacheFileName, [pTempDir UTF8String] );
strcat( cacheFileName, "/stxxlcache" );
const uint64_t kFreeSpace = (uint64_t)GetFreeDiskspace();
const uint64_t kMaxCacheSize = 512*1024*1024;
const uint64_t kCacheSize = (kFreeSpace > kMaxCacheSize) ? kMaxCacheSize : kFreeSpace;
const uint64_t kMinCacheSize = 2000 * ((1 << 15) >> 1) * sizeof( float );
stxxl::config* pCfg = stxxl::config::get_instance();
pCfg->init( cacheFileName, kCacheSize, false );
}
};
};
I then declare it inside my app delegate as follows:
stxxl::Config mCallOnceConfig;
Then when I run my application with a breakpoint inside the stxxl::Config constructor the breakpoint gets hit twice. I don't declare it anywhere else. cxx_constructor calls the constructor twice.
(It also worth noting that I've added my own stxxl::config::init function and blanked out the default one)
Edit 2: Placing a static bool around the constructor's code does sort out the double intialisation and everything works as I'd expect. Its a really rubbish solution though :(
Ok I've come up with a fairly elegant solution to my problem. I've dropped the whole class instance method and now I define my stxxl setup within the app delegates "+ (void) initialize" function. This ONLY gets called once.
Hopefully that will save some people some hassle if they suffer similar issues in the future :)

iPhone: Low memory crash

Once again I'm hunting memory leaks and other crazy mistakes in my code. :)
I have a cache with frequently used files (images, data records etc. with a TTL of about
one week and a size limited cache (100MB)). There are sometimes more then 15000 files in a
directory. On application exit the cache writes an control file with the current cache
size along with other useful information. If the applications crashes for some reason
(sh.. happens sometimes) I have in such case to calculate the size of all files on application start to make sure I know the cache size.
My app crashes at this point because of low memory and I have no clue why.
Memory leak detector does not show any leaks at all. I do not see any too. What's wrong
with the code below? Is there any other fast way to calculate the total size of all files
within a directory on iPhone? Maybe without to enumerate the whole contents of the directory?
The code is executed on the main thread.
NSUInteger result = 0;
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSDirectoryEnumerator *dirEnum = [[[NSFileManager defaultManager] enumeratorAtPath:path] retain];
int i = 0;
while ([dirEnum nextObject]) {
NSDictionary *attributes = [dirEnum fileAttributes];
NSNumber* fileSize = [attributes objectForKey:NSFileSize];
result += [fileSize unsignedIntValue];
if (++i % 500 == 0) { // I tried lower values too
[pool drain];
}
}
[dirEnum release];
dirEnum = nil;
[pool release];
pool = nil;
Thanks,
MacTouch
Draining the pool "releases" it, it doesn't just empty it. Think of autorelease pools as stacks, so you have popped yours, meaning that all these new objects are going into the main autorelease pool and not being cleaned up until it gets popped. Instead, move the creation of your autorelease pool to inside the loop. You can do something like
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
int i = 0;
while( shouldloop ) {
// do stuff
if( ++i%500 == 0 ) {
[pool drain];
pool = [[NSAutoreleasePool alloc] init];
}
}
[pool drain];

Truncated Core Data NSData objects

I am saving arrays of doubles in an NSData* object that is persisted as a binary property in a Core Data (SQLite) data model. I am doing this to store sampled data for graphing in an iPhone app. Sometimes when there are more than 300 doubles in the binary object not all the doubles are getting saved to disk. When I quit and relaunch my app there may be as few as 25 data points that have persisted or as many as 300.
Using NSSQLitePragmasOption with synchronous = FULL and this may be making a difference. It is hard to tell, as bug is intermittent.
Given the warnings about performance problems as a result of using synchronous = FULL, I am seeking advice and pointers.
Thanks.
[[Edit: here is code.]]
The (as yet unrealized) intent of -addToCache: is to add each new datum to the cache but only flush (fault?) Data object periodically.
From Data.m
#dynamic dataSet; // NSData * attribute of Data entity
- (void) addDatum:(double_t)datum
{
DLog(#"-[Data addDatum:%f]", datum);
[self addToCache:datum];
}
- (void) addToCache:(double_t)datum
{
if (cache == nil)
{
cache = [NSMutableData dataWithData:[self dataSet]];
[cache retain];
}
[cache appendBytes:&datum length:sizeof(double_t)];
DLog(#"-[Data addToCache:%f] ... [cache length] = %d; cache = %p", datum, [cache length], cache);
[self flushCache];
}
- (void) wrapup
{
DLog(#"-[Data wrapup]");
[self flushCache];
[cache release];
cache = nil;
DLog(#"[self isFault] = %#", [self isFault] ? #"YES" : #"NO"); // [self isFault] is always NO.
}
- (void) flushCache
{
DLog(#"flushing cache to store");
[self setDataSet:cache];
DLog(#"-[Data flushCache:] [[self dataSet] length] = %d", [[self dataSet] length]);
}
- (double*) bytes
{
return (double*)[[self dataSet] bytes];
}
- (NSInteger) count
{
return [[self dataSet] length]/sizeof(double);
}
- (void) dump
{
ALog(#"Dump Data");
NSInteger numDataPoints = [self count];
double *data = (double*)[self bytes];
ALog(#"numDataPoints = %d", numDataPoints);
for (int i = 0; i
I was trying to get behavior as if my Core Data entity could have an NSMutableData attribute. To do this my NSManagedObject (called Data) had an NSData attribute and an NSMutableData ivar. My app takes sample data from a sensor and appends each data point to the data set - this is why I needed this design.
On each new data point was appended to the NSMutableData and then the NSData attribute was set to the NSMutableData.
I suspect that because the NSData pointer wasn't changing (though its content was), that Core Data did not appreciate the amount of change. Calling -hasChanged on the NSManagedObjectContext showed that there had been changes, and calling -updatedObjects even listed the Data object as having changed. But the actual data that was being written seems to have been truncated (sometimes).
To work around this I changed things slightly. New data points are still appended to NSMutableData but NSData attribute is only set when sampling is completed. This means that there is a chance that a crash might result in truncated data - but for the most part this work around seems to have solved the problem.
Caveat emptor: the bug was always intermittent, so it is possible that is still there - but just harder to manifest.