Xcode - read a file containing NSDate objects - nsdate

Every time an event is triggered, my app records its date:
NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *filename = [path stringByAppendingPathComponent:#"dates.dat"];
if (![[NSFileManager defaultManager] fileExistsAtPath:filename]) {
[[NSFileManager defaultManager] createFileAtPath:filename
contents:nil
attributes:nil];
}
NSFileHandle *wHandle = [NSFileHandle fileHandleForWritingAtPath:filename];
[wHandle seekToEndOfFile];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:[NSDate date]];
[wHandle writeData:data];
[wHandle closeFile];
I successfully record the events' dates. But now I am having troubles in reading them back. I tried this but the app crashes:
NSData *restore = [NSData dataWithContentsOfFile:filename];
NSArray *date1 = [NSKeyedUnarchiver unarchiveObjectWithData:restore]; // crash here!
I notice that it takes 223 bytes for each NSDate write but that is not officially mentioned. So I am afraid that using 223 bytes as length to parse the file "dates.dat" would cause problems later.
Do you have any ideas to read dates.dat into an NSArray so I can proceed its values?
Thanks in advance

Don't store the dates as arbitrary blocks of data in a file (because you're correct, you shouldn't rely on the number of bytes used for each date).
As a minimum, use a separator character (like a carriage return) so you know which data belongs to each different date. Then you need to parse the file and read in only the appropriate data before you try to unarchive it.

Related

NSMutableArray in NSUserDefault

i've a problem with my table!
i use a parsing tableview but when i change view, my table loses data. So i decide to save all data to nsuserdefault; but, here the problem, NSUserDefault warns me:
"Note that dictionaries and arrays in property lists must also contain only property values."
NB: itemsToDisplay is a NSMutableArray and contain title, url, data and summary of parsedItems.
Well, here my code:
self.itemsToDisplay = [[[NSUserDefaults standardUserDefaults] arrayForKey:#"items"] mutableCopy];
if (!self.itemsToDisplay) {
self.itemsToDisplay = [[NSMutableArray alloc] init];
}
self.itemsToDisplay = [[NSMutableArray alloc]init];
self.itemsToDisplay = [parsedItems sortedArrayUsingDescriptors:
[NSArray arrayWithObject:[[[NSSortDescriptor alloc] initWithKey:#"date"
ascending:NO] autorelease]]];
[[NSUserDefaults standardUserDefaults] setObject:self.itemsToDisplay forKey:#"items"];
[[NSUserDefaults standardUserDefaults] synchronize];
I suppose the problem is setObject:self.itemsToDisplay, but i don't know how solve it.
Thank You guys..
First lets mention that the table cannot lose data because it does not hold any user data. The data is either provided through bindings or through delegation see NSTableViewDataSource in Apples documentation).
Second, the first three assignments to self.itemsToDisplay serve no purpose (unless you have side-effects in the setter) because they are all overridden by the last assignment.
Finally, if this code is already in the delegate then the delegate should be instantiated in your NIB file for the data to survive past a view swap. If your delegate is an object that is instantiated with your view it will also die with it along with all of the data and writing to the user-defaults is a bad idea for what you are trying to achieve. Simply set the delegate to an object whose lifetime is greater than that of both views.
self.itemsToDisplay = [parsedItems sortedArrayUsingDescriptors:
[NSArray arrayWithObject:[[[NSSortDescriptor alloc] initWithKey:#"date"
ascending:NO] autorelease]]];
//First lets encode it
NSUserDefaults *userDefault=[NSUserDefaults standardUserDefaults];
NSData *myEncodedObject = [NSKeyedArchiver archivedDataWithRootObject:self.itemsToDisplay];
[userDefault setObject:myEncodedObject forKey:[NSString stringWithFormat:#"sample"]];

Massive Mutithreading Operations

EDITED WITH NEW CODE BELOW
I'm relatively newbie on Multithreading but to achieve my goal, doing it quickly and learning something new, I decided to do it using a multithread App.
The goal: Parse a huge amount of string from a file and save every word into the SQLite db using CoreData.
Huge because the amount of words is around 300.000 ...
So this is my approach.
Step 1. Parse all the words into the file placing it into a huge NSArray. (Done quickly)
Step 2. Create the NSOperationQueue inserting the NSBlockOperation.
The main problem is that the process start very quickly but than slow down very soon. I'm Using an NSOperationQueue with max concurrent operation setted to 100. I have a Core 2 Duo Process (Dual core without HT).
I seen that using NSOperationQueue there is a lot of overhead creating the NSOperation (stopping the dispatch of the queue it need about 3 min just to create 300k NSOperation.)
CPU goes to 170% when I start dispatching the queue.
I tryed also removing the NSOperationQueue and using the GDC (the 300k loop is done instantaneous (commented lines)) but cpu used is only 95% and the problem is the same as with NSOperations. Very soon the process slow down.
Some tips to do it well?
Here some Code (Original question Code):
- (void)inserdWords:(NSArray *)words insideDictionary:(Dictionary *)dictionary {
NSDate *creationDate = [NSDate date];
__block NSUInteger counter = 0;
NSArray *dictionaryWords = [dictionary.words allObjects];
NSMutableSet *coreDataWords = [NSMutableSet setWithCapacity:words.count];
NSLog(#"Begin Adding Operations");
for (NSString *aWord in words) {
void(^wordParsingBlock)(void) = ^(void) {
#synchronized(dictionary) {
NSManagedObjectContext *context = [(PRDGAppDelegate*)[[NSApplication sharedApplication] delegate] managedObjectContext];
[context lock];
Word *toSaveWord = [NSEntityDescription insertNewObjectForEntityForName:#"Word" inManagedObjectContext:context];
[toSaveWord setCreated:creationDate];
[toSaveWord setText:aWord];
[toSaveWord addDictionariesObject:dictionary];
[coreDataWords addObject:toSaveWord];
[dictionary addWordsObject:toSaveWord];
[context unlock];
counter++;
[self.countLabel performSelectorOnMainThread:#selector(setStringValue:) withObject:[NSString stringWithFormat:#"%lu/%lu", counter, words.count] waitUntilDone:NO];
}
};
[_operationsQueue addOperationWithBlock:wordParsingBlock];
// dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// dispatch_async(queue, wordParsingBlock);
}
NSLog(#"Operations Added");
}
Thank you in advance.
Edit...
Thanks to Stephen Darlington I rewrite my code and I figured out the problem. The most important thing is: Do not share CoreData object between Thread ... it means do not mix Core data objects retrieved by different context.
This bring me to use #synchronized(dictionary) that result in a slow motion code execution!
Than I removed the massive NSOperation creation using just MAXTHREAD instance. (2 or 4 instead of 300k ... is a huge difference)
Now I can parse 300k+ String in just 30/40 seconds. Impressive!!
Still I have some issue (seams it parse more words than they are with just 1 thread and it parse not all the words if threads are more than 1 ... I need to figure it out) but now the code is really efficient. Maybe the next step could be using OpenCL and injecting it into the GPU :)
Here the new Code
- (void)insertWords:(NSArray *)words forLanguage:(NSString *)language {
NSDate *creationDate = [NSDate date];
NSPersistentStoreCoordinator *coordinator = [(PRDGAppDelegate*)[[NSApplication sharedApplication] delegate] persistentStoreCoordinator];
// The number of words to be parsed by the single thread.
NSUInteger wordsPerThread = (NSUInteger)ceil((double)words.count / (double)MAXTHREADS);
NSLog(#"Start Adding Operations");
// Here I minimized the number of threads. Every thread will parse and convert a finite number of words instead of 1 word per thread.
for (NSUInteger threadIdx = 0; threadIdx < MAXTHREADS; threadIdx++) {
// The NSBlockOperation.
void(^threadBlock)(void) = ^(void) {
// A new Context for the current thread.
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] init];
[context setPersistentStoreCoordinator:coordinator];
// Dictionary now is in accordance with the thread context.
Dictionary *dictionary = [PRDGMainController dictionaryForLanguage:language usingContext:context];
// Stat Variable. Needed to update the UI.
NSTimeInterval beginInterval = [[NSDate date] timeIntervalSince1970];
NSUInteger operationPerInterval = 0;
// The NSOperation Core. It create a CoreDataWord.
for (NSUInteger wordIdx = 0; wordIdx < wordsPerThread && wordsPerThread * threadIdx + wordIdx < words.count; wordIdx++) {
// The String to convert
NSString *aWord = [words objectAtIndex:wordsPerThread * threadIdx + wordIdx];
// Some Exceptions to skip certain words.
if (...) {
continue;
}
// CoreData Conversion.
Word *toSaveWord = [NSEntityDescription insertNewObjectForEntityForName:#"Word" inManagedObjectContext:context];
[toSaveWord setCreated:creationDate];
[toSaveWord setText:aWord];
[toSaveWord addDictionariesObject:dictionary];
operationPerInterval++;
NSTimeInterval endInterval = [[NSDate date] timeIntervalSince1970];
// Update case.
if (endInterval - beginInterval > UPDATE_INTERVAL) {
NSLog(#"Thread %lu Processed %lu words", threadIdx, wordIdx);
// UI Update. It will be updated only by the first queue.
if (threadIdx == 0) {
// UI Update code.
}
beginInterval = endInterval;
operationPerInterval = 0;
}
}
// When the NSOperation goes to finish the CoreData thread context is saved.
[context save:nil];
NSLog(#"Operation %lu finished", threadIdx);
};
// Add the NSBlockOperation to queue.
[_operationsQueue addOperationWithBlock:threadBlock];
}
NSLog(#"Operations Added");
}
A few thoughts:
Setting max concurrent operations so high is not going to have much effect. It's unlikely to be more than two if you have two cores
It looks as though you're using the same NSManagedObjectContext for all your processes. This is Not Good
Let's assume that your max concurrent operations was 100. The bottle-neck would be the main thread, where you're trying to update a label for every operation. Try to update the main thread for every n records instead of every one
You shouldn't need to lock the context if you're using Core Data correctly... which means using a different context for each thread
You don't seem to ever save the context?
Batching operations is a good way to improve performance... but see previous point
As you suggest, there's an overhead in creating a GCD operation. Creating a new one for each word is probably not optimal. You need to balance the overhead of creating a new processes with the benefits of parallelisation
In short, threading is hard, even when you use something like GCD.
It's hard to way without measuring and profiling but what looks suspicious to me is your saving the full dictionary of words that have been saved so far with the save of each word. So the amount of data per save gets successively larger and larger.
// the dictionary at this point contains all words saved so far
// which each contains a full dictionary
[toSaveWord addDictionariesObject:dictionary];
// add each time so it gets bigger each time
[dictionary addWordsObject:toSaveWord];
So, each save is saving more and more data. Why save a dictionary of all words with each word?
Some other thoughts:
why build up coreDataWords that you never use?
I wonder if you're getting the concurrency you're since you're synchronizing the full block of work.
Things to try:
comment out the dictionary on the toSaveWord in addition to the dictionary you're building up and try again - see if it's your data/data structures or DB/coreData.
Do the first but also create a serial version of it to see if you're actually getting concurency benefits.

memory leak that i cant seem to solve

So analyzer is now telling me i have a memory leak. In the function below it says 'potential leak of an object allocated into 'theAudio'
I think it speaks the truth because the app works well for a few minutes then slowly crashes.
I've tried 'autorelease' but it tells me 'object sent autorelease too many times'.
Sorry to be a pest but does anybody have any ideas on this?
-(void) playFile:(NSString*) nameOfFile { // plays audio file passed in by a string
fileLocation = nameOfFile;
NSString *path = [[NSBundle mainBundle] pathForResource:nameOfFile ofType:#"mp3"];
AVAudioPlayer* theAudio = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath: path] error:NULL];
[theAudio play];
[fileLocation release];
}
Haven't used this, but you probably need to keep a retain on the player (as you do) but then release it when you're done with it, e.g., when you get one of the AVAudioPlayerDelegate methods (so you need to implement the player's `delegate.)

How to store last part of string?

I have a string as http://www.google.co.uk/ig/images/weather/partly_cloudy.gif.
I need to save only the partly_cloudy.gif .How do i select obly that part of string?
If you are sure that it represents a path then you can call the lastPathComponent method on it.
Usage
NSString * link = #"http://www.google.co.uk/ig/images/weather/partly_cloudy.gif";
NSLog(#"%#", [link lastPathComponent]);
Use the NSString function componentsSeparatedByString. For example, NSString *url = #"http://www.google.com/1/2/3/test.gif"; NSArray *components = [url componentsSeparatedByString:#"/"]; would give you an NSArray of all strings in between a '/' character.
NSString reference: http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/Reference/NSString.html

CoreData, NSManagedObject fetch or create if not exists

I am trying to parse a lot of text files and organize their contents as managed objects. There are a lot of duplicates in the text files, so one of the "collateral" tasks is to get rid of them.
What i am trying to do in this respect is to check whether an entity with the given content exists, and if it doesn't, i create one. However, i have different entities with different attributes and relationships. What i want is a kind of function that would take a number of attributes as an input and return a new NSManagedObject instance, and i wouldn't have to worry if it was inserted into the data store or fetched from it.
Is there one?
I must also say that i am a noob at core data.
Some more detail, if you want:
I am trying to write a sort of dictionary. I have words (Word{NSString *word, <<-> Rule rule}), rules (Rule{NSString name, <->>Word word, <<->PartOfSpeech partOfSpeech, <<-> Ending endings}), parts of speech (PartOfSpeech{NSString name, <<-> Rule rule}) (i hope the notation is clear).
Two words are equal, if they have the same word property, and "linked" to the same rule. Two rules are the same, if they have the same endings and part of speech.
So far i've written a method that takes NSPredicate, NSManagedObjectContext and NSEntityDescription as an input, and first queries the datastore and returns an entity if it finds one, or creates a new one, inserts it into the datastore and returns it. However, in this case I cannot populate the new entity with the necessary data (within that method), so i have to either pass an NSDictionary with the names of attributes and their values and insert them, or return by reference a flag as to whether i created a new object or returned an old one, so that i could populate it with the data outside.
But it looks kind of ugly. I'm sure there must be something more elegant than that, i just couldn't find it. Please, help me if you can.
Your basically on the right path. Core Data is an object graph. There not a lot of dynamic built in. There's also no "upsert". like you surmise, you have to fetch and if it doesn't exist, you insert one.
Here is what I have just started using to handle a fetch-or-create scenario. I am using a top level managed object which contains a few to-many relationships to subordinate objects. I have a class that houses a few arrays of data (those are not shown here). This class is responsible for saving and retrieving to and from core data. When the class is created, I do a fetch-or-create to access my top level NSManagedObject.
#implementation MyDataManagerClass
...
#synthesize MyRootDataMO;
- (MyDataManagerClass *) init {
// Init managed object
NSManagedObjectContext *managedObjectContext = [(MyAppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
// Fetch or Create root user data managed object
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:#"MyRootDataMO" inManagedObjectContext:managedObjectContext];
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:entityDescription];
NSError *error = nil;
NSArray *result = [managedObjectContext executeFetchRequest:request error:&error];
if (result == nil) {
NSLog(#"fetch result = nil");
// Handle the error here
} else {
if([result count] > 0) {
NSLog(#"fetch saved MO");
MyRootDataMO = (MyRootDataMO *)[result objectAtIndex:0];
} else {
NSLog(#"create new MO");
MyRootDataMO = (MyRootDataMO *)[NSEntityDescription insertNewObjectForEntityForName:#"MyRootDataMO" inManagedObjectContext:managedObjectContext];
}
}
return self;
}
...

Resources