Am I missing something, or to call sendData do I really need to create 3 NS objects on the heap like this? Or is this even created on the heap? Is there anyway to create them on the stack instead? This seems inefficient!
NSData *data = [NSData dataWithBytes:packet->data length:packet->dataLength];
if(!data)
return -5;
NSString *player = [NSString initWithCString:(char*)peer->data encoding:NSASCIIStringEncoding];
if(!player)
return -6;
NSArray *to = [NSArray arrayWithObject:player];
if(!to)
return -7;
NSError *error;
BOOL success = [[GCHelper sharedInstance].match sendData:data toPlayers:to withDataMode:GKMatchSendDataReliable error:&error];
if (!success) {
printf("Error sending packet %08x %d\n", packet->data, packet->dataLength);
return -8;
}
Can I do something like this instead?
NSData data;
[data dataWithBytes:packet->data length:packet->dataLength];
NSString player;
[player initWithCString:(char*)peer->data encoding:NSASCIIStringEncoding];
NSArray to;
[to arrayWithObject:player];
Sorry for my ignorance, I am well versed with C++ but am new to Objective-C.
An alternative for you if performance is an issue. As you rightly point out in response to my comment on the question you cannot unfortunately add your own method which takes your data as C pointers as you are calling into a framework. However you can do a similar thing one level up, you can create the NSData and NSString without copying your data itself to the heap:
NSData *data = [NSData dataWithBytesNoCopy:packet->data
length:packet->dataLength
freeWhenDone:NO];
if(!data)
return -5;
NSString *player = [NSString initWithBytesNoCopy:peer->data
length:strlen(peer->data)
encoding:NSASCIIStringEncoding
freeWhenDone:NO];
if(!player)
return -6;
NSError *error;
BOOL success = [[GCHelper sharedInstance].match sendData:data
toPlayers:#[player] // array expression
withDataMode:GKMatchSendDataReliable
error:&error];
if (!success)
{
printf("Error sending packet %08x %d\n", packet->data, packet->dataLength);
return -8;
}
This still wraps your data as heap objects but doesn't both the NSData and NSString heap objects directly reference your data. You must make sure your data stays alive as long as in needed of course!
Note: If you are getting into Objective-C and will need this functionality often then you can wrap the above code up as a category on GKMatch - that is left as an exercise :-)
Can I do something like this instead?
No. Besides the fact that -init and friends do no actual initialization (take a look at NSObject.mm, all it does is return self;), you're just messaging nil with those calls. +alloc exists solely to provide an implementation-independent allocator function; one that happens to allocate objects on the heap. If you are worried about the performance of Objective-C itself, then you don't have to use it. You can drop back to C and C++ at any time and return to the land of stack-allocated variables and complex pointer arithmetic that you know and love. Objective-C is still a performant language, despite it's "inefficiencies."
Remember though: while C and C++ were designed for embedded systems and mission critical applications where memory and processor efficiency are king, Objective-C is designed to run on fairly consistent, performant, and (relatively) memory-unconstrained hardware.
Related
Just a question I had regarding general programming.
I could go for either of the options:
for(some_conditions)
{
NSFileManager * fm = [[NSFileManager alloc] init];
BOOL result = [fm moveItemAtPath:x toPath:y error:&err];
}
Or I could go for:
NSFileManager * fm = [[NSFileManager alloc] init];
for(some_conditions)
{
BOOL result = [fm moveItemAtPath:x toPath:y error:&err];
}
What I want to know is, are there any computational differences between the two with respect to time and space taken to execute the two options?
Thanks a bunch in advance :)
If you don't plan to use NSFileManagerDelegate, consider using the defaultManager:
NSFileManager *fm = NSFileManager.defaultManager;
Not only it is initialized once, but you can imagine that it might keep some internal in-memory caches to speed up certain operations.
If interested about performance you should add (and print) timings to your code and try it with real data. My guess is that here the time to alloc/init is negligible compared to the moveItemAtPath.
The space allocation can be checked in Xcode allocations instrument or debugging memory graph. My guess is that it is the same for both, because in the first variant fm is destroyed at the end of each loop iteration.
Is there a faster file system API that I can use if I only need to know if a file is a folder/symlink and its size. I'm currently using [NSFileManager attributesOfItemAtPath...] and only NSFileSize and NSFileType.
Are there any bulk filesystem enumeration APIs I should be using? I suspect this could be faster without having to jump in and out of user code.
My goal is to quickly recurse through directories to get a folders true file size and currently calling attributesOfItemAtPath is my 95% bottleneck.
Some of the code I'm currently using:
NSDictionary* properties = [fileManager attributesOfItemAtPath:filePath error:&error];
long long fileSize = [[properties objectForKey:NSFileSize] longLongValue];
NSObject* fileType = [[properties objectForKey:NSFileType isEqual:NSFileTypeDirectory];
If you want to get really hairy, the Mac OS kernel implements a unique getdirentriesattr() system call which will return a list of files and attributes from a specified directory. It's messy to set up and call, and it's not supported on all filesystems (!), but if you can get it to work for you, it can speed things up significantly.
There's also a closely related searchfs() system call which can be used to rapidly search for files. It's subject to most of the same gotchas.
You can use stat and lstat. Take a look at this answer for calculating directory size.
CPU raises with attributesOfItemAtPath:error:
Whether it's faster or not I'm not certain, but NSURL will give you this information via getResourceValue:forKey:error:
NSError * e;
NSNumber * isDirectory;
BOOL success = [URLToFile getResourceValue:&isDirectory
forKey:NSURLIsDirectoryKey
error:&e];
if( !success ){
// error
}
NSNumber * fileSize;
BOOL success = [URLToFile getResourceValue:&fileSize
forKey:NSURLFileSizeKey
error:&e];
You might also find it convenient to wrap this up if you don't really care about the error:
#implementation NSURL (WSSSimpleResourceValueRetrieval)
- (id)WSSResourceValueForKey: (NSString *)key
{
id value = nil;
BOOL success = [self getResourceValue:&value
forKey:key
error:nil];
if( !success ){
value = nil;
}
return value;
}
#end
This is given as the substitute for the deprecated File Manager function FSGetCatalogInfo(), which is used in a solution in an old Cocoa-dev thread that Dave DeLong gives the thumbs up to.
For the enumeration part, the File System Programming Guide has a section "Getting the Contents of a Directory in a Single Batch Operation", which discusses using contentsOfDirectoryAtURL:includingPropertiesForKeys:options:error:
I am working on a word-based game, and I need to load my 180,000 word dictionary (1.9MB) into an array so the solving algorithm can work with it. The dictionary is simply one word per line, like this:
a
ab
abs
absolutely
etc
...
Right now I am using the following code to load that file into the array:
NSString *txtPath = [[NSBundle mainBundle] pathForResource:#"dict" ofType:#"txt"];
NSString *stringFromFile = [[NSString alloc]
initWithContentsOfFile:txtPath
encoding:NSUTF8StringEncoding
error:&error ];
for (NSString *word in [stringFromFile componentsSeparatedByString:#"\r\n"]) {
[wordsArray addObject:word];
}
This takes about 3-4 seconds on an iPhone 4. Probably even slower on older iOS devices. Is there a faster way to do this?
You can easily do this in the background, off the main thread using Grand Central Dispatch, or GCD.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0),^(void){
NSString *txtPath = [[NSBundle mainBundle] pathForResource:#"dict" ofType:#"txt"];
NSString *stringFromFile = [[NSString alloc]
initWithContentsOfFile:txtPath
encoding:NSUTF8StringEncoding
error:&error ];
for (NSString *word in [stringFromFile componentsSeparatedByString:#"\r\n"]) {
[wordsArray addObject:word];
}
});
I wrote the enclosing dispatch code from memory, but it's close (if not correct) and I think you get the idea.
EDIT: You can execute this code on the main thread, but what happens is a non-main-thread dispatch queue is where your code executes, thereby NOT blocking the UI.
You can improve the performance of your code here a little bit by replacing the for loop with just this:
[wordsArray setArray:[stringFromFile componentsSeparatedByString:#"\r\n"]];
There should be no need to iterate over the result from -componentsSeparatedByString: (an array) just to put them into another array. With 180K words, that should be a significant time saving right there.
It would probably be faster to pre-load the word list into an SQLite database, then use SQL queries to search using whatever patterns you have. You can take advantages of indexes to make it even faster.
NSString *stringURL=[[NSString alloc] init];
stringURL=[stringURL stringByAppendingFormat:kSearchBarURL,text.text];
NSString *url = [NSString stringWithString:stringURL];
NSData *data1 = [NSData dataWithContentsOfURL:[NSURL URLWithString:url]];
NSString *responseids = [[NSString alloc] initWithData:data1 encoding:nil];
responseids = [responseids stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:#"\n\r\t"]];
SBJsonParser *parser=[SBJsonParser new];
NSData *data = [parser objectWithString:responseids error:nil];
NSMutableDictionary *searchURL = (NSMutableDictionary*)data;
I coded this i did not handle the exception for my code.
doing json and calling the service url and loading the data.
the application get crashes when my service is too low or no service found.
How to handle the exception for my code here..
Do I use #try #catch.
or
NSURLConnection for error handling.
Please help me out .
Thanks in advance.
Whenever an API makes use of NSError, you should use this rather than wrapping things up in a try…catch block as NSError is designed exactly for this. I usually reserve #try for things where I am really not able to anticipate what might go wrong. If NSError is in the mix, then you know that there is a potential for a problem that you should be handling gracefully.
More generally, your code has some strange stuff in it. You alloc init an empty NSString and then create a new string by appending a format. Not sure why you don't just use [NSString stringWithFormat]. Once you have the string, you can create the URL without the NSString *url bit.
You're also using a synchronous call to what I assume is a remote server. This has the potential to bog down your application if/when the server is not available. You're also not telling NSString what kind of encoding you expect your string to be in when it reads it from NSData. A better method depending on your server side would be to use NSString's stringWithContentsOfURL:usedEncoding:error: method. I would recommend that you use the various NSURLConnection callbacks. Have a look at the URL Loading System Programming Guide on Using NSURLConnection The NSURLConnection delegate methods are the ones you want to implement to provide this asynchronous processing.
For your trimming, you might be interested in the +whitespaceAndNewlineCharacterSet method on NSCharacterSet.
Finally, for your JSON parsing, you might be interested in the category that the SBJSON code adds to NSString, particularly -JSONValue which will give you the dictionary or array representation (as appropriate) of the NSString when parsed as JSON by SBJSON.
HTH
As an example, I want to write out some string value, str, to a file, "yy", as it changes through each iteration of a loop. Below is how I'm currently implementing it:
NSOutputStream *oStream = [[NSOutputStream alloc] initToFileAtPath:#"yy" append:NO];
[oStream open];
while ( ... )
{
NSString *str = /* has already been set up */
NSData *strData = [str dataUsingEncoding:NSUTF8StringEncoding];
[oStream write:r(uint8_t *)[strData bytes] maxLength:[strData length]];
...
}
[oStream close];
Ignoring the UTF-8 encoding, which I do require, is this how NSStrings are typically written to a file? That is, by first converting to an NSData object, then using bytes and casting the result to uint8_t for NSOutputStream's write?
Are there alternatives that are used more often?
EDIT: Multiple NSStrings need to be appended to the same file, hence the loop above.
ctshryock's solution is a bit different than yours, since you're appending as you go, and ctshyrock's write a single file all at once (overwriting the previous version). For a long-running process, these are going to be radically different.
If you do want to write as you go, I typically use NSFileHandle here rather than NSOutputStream. It's just a little bit easier to use in my opinion.
NSFileHandle *fileHandle = [NSFileHandle fileHandleForWritingAtPath:aPath];
while ( ... )
{
NSString *str = /* has already been set up */
[fileHandle writeData:[str dataUsingEncoding:NSUTF8StringEncoding]];
}
[fileHandle closeFile];
Note that fileHandle will automatically close when it is dealloced, but I like to go ahead and close it explicitly when I'm done with it.
I typically use NSString's methods writeToFile:atomically:encoding:error: or writeToURL:atomically:encoding:error: for writing to file.
See the String Programming Guide for Cocoa for more info