iPhone SDK: System() questions - ios

I got bored earlier and wondered if you could execute terminal commands on the iOS platform. Surely enough, just like OSX you can. This is really awesome, but how do I output what the terminal outputs to a text area or something similar? It's nothing serious, just a fun project.
I am using system("") to do it.

This, my friend is one of the downsides to using system. I also hope you understand that system is unavailable on a non-jailbroken iDevice, so unless you are installing it as instructed on the #1 answer on iPhone App Minus App Store, then you can't use it.
Now, moving forward, you have a few options.
Pipe the output of the command to a file, and read that file in your application. Your code should look something like this:
system("myCommand -f \"/path/to/my/file\" > output.txt")
NSString *results = [NSString stringWithContentsOfFile:#"output.txt" usedEncoding:nil error:nil];
NSLog(#"%#", results);
Create the process with the popen function, and then pipe the output directly into your application:
NSFileHandle *openProcessRead(const char *command)
{
FILE *fPtr = popen(command, "r");
NSFileHandle *fileHandle = [[NSFileHandle alloc] initWithFileDescriptor:fileno(fPtr) closeOnDealloc:YES];
return fileHandle;
}
You can now use the NSFileHandle docs to do what you need.

Related

iOS app debug without cable

Is it possible to log to a file on the iPhone (or get at the console output somehow) to read it later, or perhaps directly from the phone?
I need to debug an app that is using a cable-connected accessory device, so it cannot be connected to XCode at the same time.
You can make use of PonyDebugger.
I've been using it a lot to debug networking and CoreData resources, but it also allows logging to the console with PDLog.
PonyDebugger is a remote debugging toolset. It is a client library and gateway server combination that uses Chrome Developer Tools on your browser to debug your application's network traffic and managed object contexts.
Some recommendations:
It works better in Safari than Chrome.
Look into repo issues for making it work in your OSX version if needed. I have it working on El Capitan.
Automatic connection didn't work for me, try using local IP gateway address instead:
e.g. [debugger connectToURL:[NSURL URLWithString:#"ws://192.168.0.12:9000/device"]];
Kevin Xi's approach is OK. You can also redirect NSLog output to the file using following code in AppDelegate's didFinishLaunchingWithOptions method:
NSArray *allPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [allPaths objectAtIndex:0];
NSString *pathForLog = [documentsDirectory stringByAppendingPathComponent:#"log.txt"];
freopen([pathForLog cStringUsingEncoding:NSASCIIStringEncoding],"a+", stderr);
It is really a pity that Apple doesn't provide WiFi debugging for Xcode, but for your question, the answer is YES, you can definitely redirect NSLog to a log file, you can try this approach:
// this will redirect all NSLog in your project, add it to PCH file.
#define NSLog(args...) writeLog(__PRETTY_FUNCTION__, __LINE__, args)
void writeLog(const char *function, int lineNumber, NSString *format, ...) {
// basic log content.
va_list ap;
va_start (ap, format);
NSString *msg = [[NSString alloc] initWithFormat:format arguments:ap];
va_end (ap);
// add function name, and line number.
NSString *log = [NSString stringWithFormat:#"%s line %d $ %#", function, lineNumber, msg];
// add your own code to write `log` into a text file.
....
}
Note: if you are using some libraries which also write logs to Console, this macro won't redirect those logs to your log file, it will only work with NSLog in your own source files.
As far as I know, this is not possible.
You can eventually use a free and excellent solution like bugfender which will allow you to get the data logged by NSLog remotely. This could help you.

NSFileHandle reading corrupt data from Video file

I'm using NSFileHandle to get the data of a video as its being recorded.
It works fine notification-wise and I'm getting notified using NSFileHandleDataAvailableNotification. The problem is the video file eventually doesn't work.
Everytime when comparing the original file with the file created using NSFileHandle data, there are always just several bytes being wrong , meaning NSFileHandle reads them incorrectly.
This is how I append the data
-(void) gotData: (NSNotification *) not{
NSFileHandle *handle = not.object;
NSData *data = [handle availableData];
if(data.length){
NSLog(#"got %d", data.length);
[test appendData: data];
[handle waitForDataInBackgroundAndNotify];
}else{
NSLog(#"Ended");
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSFileHandleDataAvailableNotification object: handle];
}
}
This is how I'm writing the data eventually :
[test writeToFile:[NSTemporaryDirectory() stringByAppendingPathComponent:#"x.mp4"] atomically:YES];
And when doing a diff between the original file and the one from NSFileHandle, here are the bytes wrong (even though both are the exact same size):
I'm really clueless about this stranger behaviour, and if you ever got stuck with a similar issue I'd love your help on this.
I'm gonna go out on a limb here and say there is no way that NSFileHandle is giving you incorrect data. What I think is happening is that the data is actually being changed (by the program that is writing it out) after you have gotten the data.
I don't know what you are trying to accomplish exactly so I cant suggest a better approach.
I have some similar code that reads data from a file that is being written out by another process, only mine uses readabilityHandler instead of NSFileHandleDataAvailableNotification. I dont have much reason to believe that this would fix your problem, but it is wort a try. I know that you can run into threading issues with NSNotification so that might be something to look at.
You maybe should use [NSFileHandle offsetInFile] (and compare with your expected location based on data received) to check if things are getting rewound or something.

How to read content from plain text remote file with objective-c

I want to read a list (in plain text) from a remote file line by line.
I have found some answers but they're not the ones I'm looking for.
p.s. I've been programing in objective-c and developing in iOS for about 2 months, I'm a rookie i might not understand or recognize some terms. Please answer like you are talking to a beginner.
If i am not wrong you just want to read a text from remote file, so here it is.
NSString * result = NULL;
NSError *err = nil;
NSURL * urlToRequest = [NSURL URLWithString:#"YOUR_REMOTE_FILE_URL"];//like "http://www.example.org/abc.txt"
if(urlToRequest)
{
result = [NSString stringWithContentsOfURL: urlToRequest
encoding:NSUTF8StringEncoding error:&err];
}
if(!err){
NSLog(#"Result::%#",result);
}
To load the remote txt file, you should take a look at NSURLConnection or AFNetworking (there are other possibilities, these two are probably the most common).
You will then get the content of the file. Depending on what you intend to do with it, you may have to parse it, either with something as simple as -[NSString componentsSeparatedByString:] or with something a bit more powerful like NSScanner.
There are three steps involved in loading a file
create the object that specifies the location of the file
call the appropriate NSString class method to load the file into a
string
handle the error if the file is not found
In step 1, you need to either create an NSString with the full path to the file in the file system, or you need to create an NSURL with the network location of the file. In the example below, the code creates an NSURL since your file is on the network.
In step 2, use the stringWithContentsOfFile method to load a file from the file system, or the stringWithContentsOfURL method to load a file from the network. In either case, you can specify the file encoding, or ask iOS to auto-detect the file encoding. The code below auto detects while loading from the network.
In step 3, the code below dumps the file to the debug console if successful or dumps the error object to the console on failure.
Missing from this code is multithreading. The code will block until the file is loaded. Running the code on a background thread, and properly notifying the main thread when the download is complete, is left as an exercise for the reader.
NSURL *url = [NSURL URLWithString:#"www.example.com/somefile.txt"];
NSStringEncoding encoding;
NSError *error;
NSString *str = [NSString stringWithContentsOfURL:url usedEncoding:&encoding error:&error];
if ( str )
NSLog( #"%#", str );
else
NSLog( #"%#", error );

iOS - NSFileHandle availableData hangs only when app is run manually on device

I have a file handle set up to read the contents of stdout, and when I try to pull the data out of it using availableData it hangs, but only when the app is run manually on my device. When I run the app on my device through Xcode or on the simulator, it pulls the data out as expected and the rest of my code works perfectly. Any ideas what is happening? My code is below:
int pipefd[2];
pipe(pipefd);
dup2(pipefd[1], STDOUT_FILENO);
close(pipefd[1]);
NSFileHandle *stdoutReader = [[NSFileHandle alloc] initWithFileDescriptor:pipefd[0]];
// this method writes output to stout
int result = [self createOutput:string1:string2];
// code hangs on this next line when app is run manually on device)
NSData *stdoutData = [stdoutReader availableData];
I am wondering if on my device, the createOutput method runs more slowly and so by the time I try to get data out of stdout there isn't any yet?
Well, it looks like iOS 5.1 doesn't allow writing to stdout anymore. For anyone interested in reading more, here's an informative blog post: http://spouliot.wordpress.com/2012/03/13/ios-5-1-vs-stdout/

What is the fastest way to load a large CSV file into core data

Conclusion
Problem closed, I think.
Looks like the problem had nothing to do with the methodology, but that the XCode did not clean the project correctly in between builds.
It looks like after all those tests, the sqlite file that was being used was still the very first one that wasn't indexed......
Beware of XCode 4.3.2, I have nothing but problems with Clean not cleaning, or adding files to project not automatically being added to the bundle resources...
Thanks for the different answers..
Update 3
Since I invite anybody to just try the same steps to see if they get the same results, let me detail what I did:
I start with blank project
I defined a datamodel with one Entity, 3 attributes (2 strings, 1 float)
The first string is indexed
In did finishLaunchingWithOptions, I am calling:
[self performSelectorInBackground:#selector(populateDB) withObject:nil];
The code for populateDb is below:
-(void)populateDB{
NSLog(#"start");
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
NSManagedObjectContext *context;
if (coordinator != nil) {
context = [[NSManagedObjectContext alloc] init];
[context setPersistentStoreCoordinator:coordinator];
}
NSString *filePath = [[NSBundle mainBundle] pathForResource:#"input" ofType:#"txt"];
if (filePath) {
NSString * myText = [[NSString alloc]
initWithContentsOfFile:filePath
encoding:NSUTF8StringEncoding
error:nil];
if (myText) {
__block int count = 0;
[myText enumerateLinesUsingBlock:^(NSString * line, BOOL * stop) {
line=[line stringByReplacingOccurrencesOfString:#"\t" withString:#" "];
NSArray *lineComponents=[line componentsSeparatedByString:#" "];
if(lineComponents){
if([lineComponents count]==3){
float f=[[lineComponents objectAtIndex:0] floatValue];
NSNumber *number=[NSNumber numberWithFloat:f];
NSString *string1=[lineComponents objectAtIndex:1];
NSString *string2=[lineComponents objectAtIndex:2];
NSManagedObject *object=[NSEntityDescription insertNewObjectForEntityForName:#"Bigram" inManagedObjectContext:context];
[object setValue:number forKey:#"number"];
[object setValue:string1 forKey:#"string1"];
[object setValue:string2 forKey:#"string2"];
NSError *error;
count++;
if(count>=1000){
if (![context save:&error]) {
NSLog(#"Whoops, couldn't save: %#", [error localizedDescription]);
}
count=0;
}
}
}
}];
NSLog(#"done importing");
NSError *error;
if (![context save:&error]) {
NSLog(#"Whoops, couldn't save: %#", [error localizedDescription]);
}
}
}
NSLog(#"end");
}
Everything else is default core data code, nothing added.
I run that in the simulator.
I go to ~/Library/Application Support/iPhone Simulator/5.1/Applications//Documents
There is the sqlite file that is generated
I take that and I copy it in my bundle
I comment out the call to populateDb
I edit persistentStoreCoordinator to copy the sqlite file from bundle to documents at first run
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
#synchronized (self)
{
if (__persistentStoreCoordinator != nil)
return __persistentStoreCoordinator;
NSString *defaultStorePath = [[NSBundle mainBundle] pathForResource:#"myProject" ofType:#"sqlite"];
NSString *storePath = [[[self applicationDocumentsDirectory] path] stringByAppendingPathComponent: #"myProject.sqlite"];
NSError *error;
if (![[NSFileManager defaultManager] fileExistsAtPath:storePath])
{
if ([[NSFileManager defaultManager] copyItemAtPath:defaultStorePath toPath:storePath error:&error])
NSLog(#"Copied starting data to %#", storePath);
else
NSLog(#"Error copying default DB to %# (%#)", storePath, error);
}
NSURL *storeURL = [NSURL fileURLWithPath:storePath];
__persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error])
{
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return __persistentStoreCoordinator;
}
}
I remove the app from the simulator, I check that ~/Library/Application Support/iPhone Simulator/5.1/Applications/ is now removedI rebuild and launch again
As expected, the sqlite file is copied over to ~/Library/Application Support/iPhone Simulator/5.1/Applications//Documents
However the size of the file is smaller than in the bundle, significantly!
Also, doing a simple query with a predicate like this predicate = [NSPredicate predicateWithFormat:#"string1 == %#", string1]; clearly shows that string1 is not indexed anymore
Following that, I create a new version of the datamodel, with a meaningless update, just to force a lightweight migration
If run on the simulator, the migration takes a few seconds, the database doubles in size and the same query now takes less than a second to return instead of minutes.
This would solve my problem, force a migration, but that same migration takes 3 minutes on the iPad and happens in the foreground.
So hat's where I am at right now, the best solution for me would still be to prevent the indexes to be removed, any other importing solution at launch time just takes too much time.
Let me know if you need more clarifications...
Update 2
So the best result I have had so far is to seed the core data database with the sqlite file produced from a quick tool with similar data model, but without the indexes set when producing the sqlite file. Then, I import this sqlite file in the core data app with the indexes set, and allowing for a lightweight migration. For 2 millions record on the new iPad, this migration stills take 3 minutes. The final app should have 5 times this number of records, so we're still looking at a long long processing time.
If I go that route, the new question would be: can a lightweight migration be performed in the background?
Update
My question is NOT how to create a tool to populate a Core Data database, and then import the sqlite file into my app. I know how to do this, I have done it countless times. But until now, I had not realized that such method could have some side effect: in my case, an indexed attribute in the resulting database clearly got 'unindexed' when importing the sqlite file that way.
If you were able to verify that any indexed data is still indexed after such transfer, I am interested to know how you proceed, or otherwise what would be the best strategy to seed such database efficiently.
Original
I have a large CSV file (millions of lines) with 4 columns, strings and floats.
This is for an iOS app.
I need this to be loaded into core data the first time the app is loaded.
The app is pretty much non functional until the data is available, so loading time matters, as a first time user obviously does not want the app to take 20 minutes to load before being able to run it.
Right now, my current code takes 20 min on the new iPad to process a 2 millions line csv file.
I am using a background context to not lock the UI, and save the context every 1,000 records
The first idea I had was to generate the database on the simulator, then to copy/paste it in the document folder at first launch, as this is the common non official way of seeding a large database. Unfortunately, the indexes don't seem to survive such a transfer, and although the database was available after just a few seconds, performance is terrible because my indexes were lost. I posted a question about the indexes already, but there doesn't seem to be a good answer to that.
So what I am looking for, either:
a way to improve performance on loading millions of records in core data
if the database is pre-loaded and moved at first startup, a way to keep my indexes
best practices for handling this kind of scenario. I don't remember using any app that requires me to wait for x minutes before first use (but maybe The Daily, and that was a terrible experience).
Any creative way to make the user wait without him realizing it: background import while going through tutorial, etc...
Not Using Core Data?
...
Pre-generate your database using an offline application (say, a command-line utility) written in Cocoa, that runs on OS X, and uses the same Core Data framework that iOS uses. You don't need to worry about "indexes surviving" or anything -- the output is a Core Data-generated .sqlite database file, directly and immediately usable by an iOS app.
As long as you can do the DB generation off-line, it's the best solution by far. I have successfully used this technique to pre-generated databases for iOS deployment myself. Check my previous questions/answers for a bit more detail.
I'm just starting out with SQLite and I need to integrate a DB into one of my apps that will have a lot of indexed data in a SQLite database. I was hoping I could do some method where I could bulk insert my information into a SQLite file and add that file to my project. After discovering and reading through your question, the provided answer and the numerous comments, I decided to check out the SQLite source to see if I could make heads or tails of this issue.
My initial thought was that the iOS implementation of SQLite is, in fact, throwing out your indices. The reason is because you initially create your DB index on x86/x64 system. The iOS is an ARM processor, and numbers are handled differently. If you want your indexes to be fast, you should generate them in such a way that they are optimized for the processor in which they will be searched.
Since SQLite is for multiple platforms, it would make since to drop any indices that have been created in another architecture and rebuild them. However, since no one wants to wait for an index to rebuild the first time it is accessed, the SQLite devs most likely decided to just drop the index.
After digging into the SQLite code, I've come to the conclusion that this is most likely happening. If not for the processor architecture reason, I did find code (see analyze.c and other meta-information in sqliteint.h) where indices were being deleted if they were generated under an unexpected context. My hunch is that the context that drives this process is how the underlying b-tree data structure was constructed for the existing key. If the current instance of SQLite can't consume the key, it deletes it.
It is worth mentioning that the iOS Simulator is just that-- a simulator. It is not an emulator of the, hardware. As such, your app is running in a pseudo-iOS device, running on an x86/x64 processor.
When your app and SQLite DB are loaded to your iOS device, an ARM-compiled variant is loaded, which also links to the ARM compiled libraries within iOS. I couldn't find ARM specific code associated with SQLite, so I imagine Apple had to modify it to their suit. The could also be part of the problem. This may not be an issue with the root-SQLite code, it could be an issue with the Apple/ARM compiled variant.
The only reasonable solution that I can come up with is that you can create a generator application that you run on your iOS machine. Run the application, build the keys, and then rip the SQLite file from the device. I'd imagine such a file would work across all devices, since all ARM processors used by iOS are 32-bit.
Again, this answer is a bit of an educated guess. I'm going to re-tag your question as SQLite. Hopefully a guru may find this and be able to weigh in on this issue. I'd really like to know the truth for my own benefit.

Resources