I've been storing images (JPEG) in CoreData (SQLite store) for a while without problem. I've recently had to import some data from an external source and it appeared to be working fine with the new data. I've just come across an instance of data where the following applies:
1. The entities load fine and have the relevant information
2. The entity relationships are restored
3. The entity that contains the NSData property returns nil for that property
4. When I query the backing store for that object it has binary data showing in the equivalent column
When I've looked into the store further it appears that the problem rows in question only have 38 bytes of information stored in the binary data column. Consistently 30% of all of the images that are processed have this 38 bytes of data stored while the remainder have completely accurate and correct data stored.
The import process runs in a background thread. In order to manage the memory requirements of 4.5Gb of image data I run the import in batches of 100-1000 (initially 1000 but have tried 100 to see if any difference). I create an nsmanagedobjectcontext at the beginning of the batch, process the batch then save the context. During the processing I NSLog the length of data being assigned to the NSData property of the objects in question and they are accurate 100% of the time. However when the data is saved and I look in the store 30% have only 38 bytes of data. I have also tried saving the context after every object is created but have the same 30% dud result.
I have a strong feeling there is an element of multi-threading screwing up my import process but I can't find anything online about this. Would love some input from a CoreData expert as to what the hell is going on with my data!!!
Edit: A subset of the code (there is a lot of other stuff going on in the import process but the image import is reasonably linear)
NSArray *importLines = [[importFileContents componentsSeparatedByString:#"\r\n"] retain];
int batch = 100;
int currentBatch = 0;
int totalLines = importLines.count;
while(currentBatch*batch<totalLines)
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];
// Create a live context
NSManagedObjectContext *liveContext = [[PRModel sharedInstance] newLiveManagedObjectContext];
NSMutableArray *errorMessages = [[NSMutableArray alloc]init];
for(int i=currentBatch*batch; i<(currentBatch+1)*batch && i<totalLines; i++)
{
NSString *importLine = [importLines objectAtIndex:i];
NSArray *importLineData = [importLine componentsSeparatedByString:#"~"];
if([importLineData count]>0)
{
NSString *lineType = [importLineData objectAtIndex:0];
int lineTypeID = [lineType intValue];
NSString *errorMessage = nil;
switch(lineTypeID)
{
// other cases handle other object types and all work fine
case 5:
errorMessage = [DataImporter processPhoto:importLineData withRefDate:refDate intoContext:liveContext];
// try saving every object to see if it makes an effect
[Utils saveManagedObjects:liveContext];
break;
default:
break;
}
if(errorMessage!=nil)
{
[errorMessages addObject:errorMessage];
}
}
}
[Utils saveManagedObjects:liveContext];
[liveContext release];
// Update the errors/missing areas/missing items files in case we get a memory crash
[DataImporter writeErrors:errorMessages];
[errorMessages release];
currentBatch++;
NSLog(#"Batch %d complete",currentBatch);
[pool release];
}
PRModel newLiveManagedObjectContext
-(NSManagedObjectContext*)newLiveManagedObjectContext
{
NSPersistentStoreCoordinator *coordinator = [self livePSC];
NSManagedObjectContext *newContext = nil;
if (coordinator != nil) {
newContext = [[NSManagedObjectContext alloc] init];
[newContext setPersistentStoreCoordinator: coordinator];
}
return newContext;
}
- (NSPersistentStoreCoordinator *)livePSC {
if (livePSC != nil) {
return livePSC;
}
LiveDatabaseUpgradeController *upgrader = [[LiveDatabaseUpgradeController alloc]initWithDelegate:nil];
#try{
livePSC = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.currentModel];
NSError *error = nil;
if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:[NSURL fileURLWithPath:[self currentModelStorePath]] options:nil error:&error])
{
NSLog(#"Failed To Open Data Store. Error:%#, %#", error, [error userInfo]);
#throw [NSException exceptionWithName:#"Upgrade Required!" reason:#"attempted to open your database but failed. To quit press the home button." userInfo:nil];
}
}
#catch(NSException *e){
[Utils showAlert:[e name] withMessage:[e reason] withDelegate:nil];
}
#finally{
[upgrader release];
}
return livePSC;
}
The processPhoto method
+(NSString*)processPhoto:(NSArray*)data withRefDate:(NSDate*)refDate intoContext:(NSManagedObjectContext*)liveContext// withData:(bool)saveData
{
if(data.count!=10)
return [NSString stringWithFormat:#"Expected 10 items, found %d",[data count]];
NSString *partialPath = [data objectAtIndex:8];
if([partialPath isEqualToString:#"NULL"])
return nil;
NSString *filePath = [[[PRModel sharedInstance] applicationDocumentsDirectory] stringByAppendingPathComponent:partialPath];
if(![[NSFileManager defaultManager] fileExistsAtPath:filePath])
{
return [NSString stringWithFormat:#"File not found for %#",filePath];
}
// Got everything we need so create a photo
PhotoEntity *photo = [NSEntityDescription insertNewObjectForEntityForName:#"Photo" inManagedObjectContext:liveContext];
photo.photoID = [data objectAtIndex:9];
photo.imageType = #"PHOTO";
photo.photoDescription = #"";
UIImage *photoImage = [[UIImage alloc]initWithContentsOfFile:filePath];
[photo setImage:photoImage inContext:liveContext];
[photoImage release];
return nil;
}
The setImage method is where the data is actually set
-(void)setImage:(UIImage *)image inContext:(NSManagedObjectContext *)context
{
if(self.photoData==nil)
{
// Create a new photo data entity
self.photoData = [NSEntityDescription insertNewObjectForEntityForName:#"PhotoData" inManagedObjectContext:context];
}
UIImage *resizedImage = [image copyImageResizedWithLength:MAX_STORE_RESOLUTION interpolationQuality:kCGInterpolationHigh];
NSData *data = UIImageJPEGRepresentation(resizedImage, 0.6);
NSLog(#"ImageLength=%d",[data length]);
self.photoData.imageData = data;
[resizedImage release];
}
So this code works in 70% of cases but fails in 30%... There's a whole bunch of other stuff going on around this but the code for handling images has been rock solid for at least 6 months in the field with 500 users so it's definitely related to the fact that I'm using it in a mass object background thread process.
Sigh... It turns out that CoreData has decided to store files > 130Kb as external data now. The 38 bytes I am seeing in the store is the Guid relating to the external file. Now I need to work out why valid data is returning as nil in the main body of my app when there is clearly data present.
Edit:
Ok, worked out the missing data issue as well. I need to run the import on the simulator to maximise the available RAM. I then transferred the database (minus external data folder because I didn't realise it was there) to an iPad for testing... When the app loaded the data it attempted to retrieve the external data file but couldn't find it and so loaded the data property as nil. There weren't any exceptions or errors indicating this was the case which is frustrating to say the least.
Related
I'm totally frustrated with this. I have a scene which reports on contents previously stored in Core Data. It was working and preparing the contents to be emailed. Suddenly it stopped working properly and the Fetch request returned an empty result state. I spent many hours on the Simulator without success. Then I decided to test it on my iPhone and it worked. I was very happy until I decided to test it again and it crashed my iPhone. When I attempted to test it again it did not return any data. However other scenes that fetch and store data using essentially the same code are working. I restored an earlier version of the script and it behaves the same way on the simulator (I did not test it on the iPhone.)
The only differences between the two screens is that the one that is not working uses a ScrollView and has this interface: #interface CREWEmail : UIViewController
I tested it on the previous scene (without the scrollview) and it still does not return the results. It looks like the problem may be due to the MFMailComposeViewControllerDelegate
Can anyone suggest what could cause this strange behaviour and how I can fix it?
I've included the relevant portion of the script below:
- (void) processAllViewNotes {
CREWAppDelegate *appDelegate = [[UIApplication sharedApplication]delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSEntityDescription *entityDesc = [NSEntityDescription entityForName:#"View" inManagedObjectContext:context];
NSFetchRequest * request = [[NSFetchRequest alloc] init];
[request setEntity:entityDesc];
// Execute Fetch Request
NSError *fetchError = nil;
NSArray * objects = [appDelegate.managedObjectContext executeFetchRequest:request error:&fetchError]; // was * objects
NSLog(#"Objects ===> %#",objects);// NSLog(#" objects count %lu", (unsigned long)[objects count]); // zero ... why?
if (fetchError) {
NSLog (#"getNotes Fetch error:%#", fetchError); // not displaying so no error?
};
NSString *viewName;
NSString * viewDescription;
if (! [objects count] == 0) {
// NSLog(#" viewName %#", viewName);
int i;
NSManagedObjectContext * matches = nil;
NSLog(#" objects count %lu", (unsigned long)[objects count]);
for (i=0; i < [objects count]; i++) {
NSLog(#" i %i",i);
matches = objects [i];
// NSLog(#"matches %#",matches);
viewName = [matches valueForKey : #"viewName"];
viewDescription = [matches valueForKey : #"viewDescription"];
NSString *viewNotes = [self processNotes:viewName];
self.textView.text = [NSString stringWithFormat:#"%#\n%#\n%#",self.textView.text,viewDescription,viewNotes]; // add view name and notes to text view
// get matching switches (if any)
NSString *switchText = [self processSwitches:viewName];
self.textView.text = [NSString stringWithFormat:#"%#%#\n", self.textView.text,switchText];
} // end for
} // end if count not zero
else { // count = 0
// self.textView.text = #"no notes yet";
}
}
Ok, im a bit lost with this one, i am currently trying to run a background core data operation using a second ManagedObjectContext with its type set to NSPrivateQueueConcurrencyType and failing miserably with the above error.
I have a custom subclass of NSOperation, which is being passed an NSArray of strings, and the PersistentStoreCoordinator from the main thread, it then creates its own ManagedObjectContext, runs a query and performs and operation.
Here is the code from the class:
//
// ProcessProfanity.m
// Hashtag Live Desktop
//
// Created by Gareth Jeanne on 24/03/2014.
// Copyright (c) 2014 Gareth Jeanne. All rights reserved.
//
#import "ProcessProfanity.h"
#import "Tweet.h"
static const int ImportBatchSize = 250;
#interface ProcessProfanity ()
#property (nonatomic, copy) NSArray* badWords;
#property (nonatomic, strong) NSManagedObjectContext* backgroundContext;
#property (nonatomic, strong) NSPersistentStoreCoordinator* persistentStoreCoordinator;
#end
#implementation ProcessProfanity
{
}
- (id)initWithStore:(NSPersistentStoreCoordinator*)store badWords:(NSArray*)words
{
self = [super init];
if(self) {
self.persistentStoreCoordinator = store;
self.badWords = words;
}
return self;
}
- (void)main
{
_backgroundContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
_backgroundContext.persistentStoreCoordinator = [self persistentStoreCoordinator];
_backgroundContext.undoManager = nil;
[_backgroundContext performBlockAndWait:^
{
[self import];
}];
}
- (void)import
{
//Create new fetch request
NSFetchRequest *request = [[NSFetchRequest alloc] init];
//Setup the Request
[request setEntity:[NSEntityDescription entityForName:#"Tweet" inManagedObjectContext:self.backgroundContext]];
NSError *error = nil;
//Create an array from the returned objects
NSArray* tweetsToProcess = [self.backgroundContext executeFetchRequest:request error:&error];
NSAssert2(tweetsToProcess != nil && error == nil, #"Error fetching events: %#\n%#", [error localizedDescription], [error userInfo]);
for (Tweet* tweetToCheck in tweetsToProcess){
__block NSString *result = nil;
[self.badWords indexOfObjectWithOptions:NSEnumerationConcurrent
passingTest:^(NSString *obj, NSUInteger idx, BOOL *stop)
{
if (tweetToCheck){
if ([tweetToCheck.text rangeOfString:obj].location != NSNotFound)
{
result = obj;
*stop = YES;
//return YES;
}
}
return NO;
}];
if (!result){
//DDLogVerbose(#"The post does not contain any of the words from the naughty list");
if(tweetToCheck){
tweetToCheck.profanity = [NSNumber numberWithBool:false];
}
}
else{
if(tweetToCheck){
//DDLogVerbose(#"The string contains '%#' from the the naughty list", result);
tweetToCheck.profanity = [NSNumber numberWithBool:true];
}
}
}
[self.backgroundContext save:NULL];
}
#end
And this is how i am calling it:
-(void)checkForProfanity{
if(!self.operationQueue){
self.operationQueue = [[NSOperationQueue alloc] init];
}
NSArray* termsToPass = [self.filterTerms copy];
ProcessProfanity* operation = [[ProcessProfanity alloc] initWithStore:self.persistentStoreCoordinator badWords:termsToPass];
[self.operationQueue addOperation:operation];
}
Edit 1
The specific line i seem to be getting the error on, or at least where Xcode is breaking is:
if ([tweetToCheck.text rangeOfString:obj].location != NSNotFound)
I have managed to narrow this down a bit, the NSArray that contains the list of terms to search the strings for is potentially quite large, possibly over a 1,000 NSStrings. If i test with an array of that size, i get the issue. However if i reduce the array to around 15 NSStrings, i do not get the error, so i don't think this is necessarily a thread related issue, i'm wondering if the array is getting released in the main thread. I have modified the code to make a deep copy, and then a __block copy as follows, but it doesn't seem to have helped.
self.badWords = [[NSArray alloc] initWithArray:words copyItems:YES];
and
for (Tweet* tweetToCheck in tweetsToProcess){
__block NSArray *array = [[NSArray alloc] initWithArray:self.badWords copyItems:YES];
__block NSString *result = nil;
[array indexOfObjectWithOptions:NSEnumerationConcurrent
In fact, at the point where Xcode breaks, if i PO array, i get an object not found message, but if i po result, i correct get an object returned that is nil.
Edit 2
So i have made the following changes, with no change:
Made the NSArray strong rather than copy:
#property (nonatomic, strong) NSArray* badWords;
And made it a copy when allocated:
self.badWords = [[NSArray alloc] initWithArray:words copyItems:YES];
And created a local copy of the NSArray with the ___block declaration inside the actual method processing the objects:
__block NSArray *array = [[NSArray alloc] initWithArray:self.badWords copyItems:YES];
Which should surely mean it sticks around for the life of the ProcessProfanity object?
Am i wrong in expecting to be able to PO the array from the breakpoint within the block?
In this instance the error message "error: NULL _cd_rawData but the object is not being turned into a fault" indicates that you are accessing a managed object outside of its context. Basically your fetch returns all the Tweets from your persistent store as faults. Once you try and access a property on the Managed Object, Core Data will fire a fault and fetch the full object from the store.
By calling the NSArray method indexOfObjectWithOptions:passingTest: with an option of NSEnumerationConcurrent you are implying that you want to perform asynchronous execution on the elements in your array. The keyword concurrent indicates that multiple threads can be used to operate on the array elements.
In your context this means that accessing a managed object inside this block might result in accessing it on a different thread from the managed object context that owns the object. So when you access tweetToCheck.text in your conditional check - if ([tweetToCheck.text rangeOfString:obj].location != NSNotFound), under the hood Core Data is fetching that managed object from the persistent store and returning it to a thread that is not part of the managed object contexts thread.
Furthermore, it is not necessary to use the method indexOfObjectWithOptions:passingTest: since you are not actually interested in the result of this operation.
It seems to me that it might be more convenient for you to use an NSSet as you are only testing to see whether or not a given tweet word exists in your profane words. Quoting the documentation for NSSet: "You can use sets as an alternative to arrays when the order of elements isn’t important and performance in testing whether an object is contained in the set is a consideration". Clearly this seems to meet your criteria.
So your init would look like:
-(id)initWithStore:(NSPersistentStoreCoordinator*)store
badWords:(NSSet*)badWords
{
self = [super init];
if(self) {
self.persistentStoreCoordinator = store;
self.badWords = [words copy];
}
return self;
}
Since you are only interested in updating tweets that have not yet been tagged for profanity you would probably only want to fetch tweets that haven't been flagged profane:
//Create new fetch request
NSFetchRequest *request = [[NSFetchRequest alloc] init];
//Setup the Request
[request setEntity:[NSEntityDescription entityForName:#"Tweet" inManagedObjectContext:self.backgroundContext]];
[request setPredicate:[NSPredicate predicateWithFormat:#"profanity = NO"]];
Now that you have an array of tweets that are not profane you could iterate through your tweets and check each word if it contains a profane word. The only thing you will need to deal with is how to separate your tweet into words (ignoring commas and exclamation marks etc). Then for each word you are going to need to strip it of diacritics and probably ignore the case. So you would end up with someone along the lines of:
if([self.badWords containsObject:badWordString]) {
currentTweet.profanity = [NSNumber numberWithBOOL:YES];
}
Remember, you can run predicates on an NSSet so you could actually perform a case and diacritic insensitive query:
NSPredicate *searchPredicate = [NSPredicate predicateWithFormat:#"SELF = %#[cd]",wordToCheck];
BOOL foundABadWord = ([[[self.badWords filteredSetUsingPredicate:searchPredicate] allObjects] count] > 0);
Another thing you might want to consider is removing duplicate words in your tweets, you don't really want to perform the same check multiple times. So depending on how you find the performance you could place each word of your tweet into an NSSet and simply run the query on the unique words in your tweet:
if([[self.badWords intersectsSet:tweetDividedIntoWordsSet]) {
//we have a profane tweet here!
}
Which implementation you choose is up to you but assuming you are only using english in your app you are definitely going to want to run a case and diacritic insensitive search.
EDIT
One final thing to note is that no matter how much you try, people will always be the best means of detecting profane or abusive language. I encourage you to read this SO's post on detecting profanity - How do you implement a good profanity filter?
Ok, so still not quite sure what was going on, but i followed Daniels advice and re-wrote the indexOfObjectWithOptions method and now it's working. For completeness, and so it hopefully helps someone else, this is what i ended up doing.
DDLogInfo(#"Processing posts to check for bad language");
for (Tweet* tweetToCheck in tweetsToProcess){
__block NSArray *array = [[NSArray alloc] initWithArray:self.badWords copyItems:YES];
__block NSString *result = nil;
NSRange tmprange;
for(NSString *string in array) {
tmprange = [tweetToCheck.text rangeOfString:[NSString stringWithFormat:#" %# ", string]];
if (tmprange.location != NSNotFound) {
result = string;
DDLogVerbose(#"Naughty Word Found: %#", string);
break;
}
}
if (!result){
//DDLogVerbose(#"The post does not contain any of the words from the naughty list");
if(tweetToCheck){
tweetToCheck.profanity = [NSNumber numberWithBool:false];
}
}
else{
if(tweetToCheck){
//DDLogVerbose(#"The string contains '%#' from the the naughty list", result);
tweetToCheck.profanity = [NSNumber numberWithBool:true];
}
}
Here is what I want to achieve. 1. Searching all the files 2. find all .jpg files during the searching 3. save all .jpg file paths into NSMutableArray
Here are the codes:
Created the NSMutableArray:
NSMutableArray *jpgFiles = [[[NSMutableArray alloc]init]autorelease];
Searching all the parent folders under (/Users/) path (Start NSThread in here):
NSString* filePath = [url path];
NSArray *dirFiles = [[NSFileManager defaultManager]contentsOfDirectoryAtPath:filePath error:nil];
if([dirFiles count]!=0)
{
for (int j=0; j<[dirFiles count]; j++) {
NSString* pathExtension = [[dirFiles objectAtIndex:j] pathExtension];
//if extension is null, we forwards to next level.
if ([pathExtension isEqualTo:#""])
{
#autoreleasepool {
[NSThread detachNewThreadSelector:#selector(searchingPicture:) toTarget:self withObject:[filePath stringByAppendingPathComponent:[dirFiles objectAtIndex:j]]];
}
}
else
{
//if find jpg in this level, save into array
if([pathExtension isEqualTo:#"JPG"])
{
[jpgFiles addObject:[filePath stringByAppendingPathComponent:[dirFiles objectAtIndex:j]]];
}
}
}
}
Keep searching the rest of sub folders and save proper file path into array:
-(void)searchingPicture:(NSString*)path
{
NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease];
NSURL *directoryURL = [NSURL URLWithString:[path stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
NSArray *keys = [NSArray arrayWithObject:NSURLIsDirectoryKey];
NSDirectoryEnumerator *enumerator = [fileManager
enumeratorAtURL:directoryURL
includingPropertiesForKeys:keys
options:0
errorHandler:^(NSURL *url, NSError *error) {
// Handle the error.
// Return YES if the enumeration should continue after the error.
return YES;
}];
for (NSURL *url in enumerator) {
NSError *error;
NSNumber *isDirectory = nil;
if (! [url getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:&error]) {
// handle error
}
else if (! [isDirectory boolValue]) {
// No error and it’s not a directory; do something with the file
if([[[url path] pathExtension]isEqualTo:#"JPG"])
{
//This line gives me error !!!
[jpgFiles addObject:[url path]];
}
}
}
}
Error: (At beginning, it works fine and save many different files into array, but after saved around 50 files it starts to give me error and crash at the end).
Here is the correct element adds into array:
/Users/NAME/Documents/Graduate Books/IMG_2517.JPG
Here is the error message:
-[NSPathStore2 addObject:]: unrecognized selector sent to instance 0x10011d4d0
However, even this error occurs, it still keeps saving some of paths into array and then it will throw another error:
An uncaught exception was raised
Could you guys tell me how to fix it?? Thanks !!
First, trying to increase performance by randomly spawning threads is guaranteed failure. Concurrency must be considered and controlled.
Secondly, trying to decrease execution time of code that is accessing a slow resource (like the filesystem) by concurrently accessing said resource without constraint will be slower than serialized access. I/O to filesystems is relatively slow and linear I/O is always faster than concurrent, conflicted, random I/O.
Finally, NSMutableDictionary is not thread safe. Nor are the other mutable collection classes. If you are shoving stuff into collections from multiple threads, you'll see undefined behavior (typically crashes).
NSMutableArray is not thread safe. It is thread safe when you guard it correctly -- so that no more than one thread is able to use it any time.
To illustrate:
- (void)addPath:(NSString *)pPath
{
[self.lock lock];
[self.files addObject:pPath];
[self.lock unlock];
}
- (NSUInteger)countPaths
{
[self.lock lock];
const NSUInteger count = self.files.count;
[self.lock unlock];
return count;
}
- (NSArray *)copyPaths
{
[self.lock lock];
NSArray * paths = [self.files copy];
[self.lock unlock];
return paths;
}
And as bbum pointed out, directory enumeration as seen in your example is not a problem which lends itself well to parallelization -- Parallelization hurts in this scenario. A more practical approach would be to enumerate from just one thread. If you want to immediately load some of the images, just load them from the "I/O thread" as well.
Should I check if a file exists at a given path when trying to read the data into an NSData object? Right now I'm not doing at the moment but my code works since my array that is supposed to get populated with data from the file will be set to nil and I can later in my application check if that array is nil. But I'm thinking that the code is perhaps "ugly" and would work better if i did a check (like the one in the commented code).
NSData *codedData = [[NSData alloc] initWithContentsOfFile:[CareersParser dataFilePath]];
/*
if (codedData != nil) {
//do the below (not commented out code)
} else {
//file doesn't exist at the given path
[codedData release];
return nil;
}
*/
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:codedData];
NSArray *careers = [unarchiver decodeObjectForKey:kDataKey];
[unarchiver finishDecoding];
NSMutableArray *ids = [[NSMutableArray alloc] init];
for (Career *career in careers) {
[ids addObject:[career id]];
}
// Sort the array in an orderly fashion
NSArray *sortedIds = [ids sortedArrayUsingSelector:#selector(caseInsensitiveCompare:)];
// Release allocated memory
[ids release];
[unarchiver release];
[codedData release];
return sortedIds;
That seems sensible to me I would reverse the if statement to do away with the else clause.
if (nil == codedDate) {
[codedData release];
return nil;
}
... Do the rest of your code
Apple suggests here that you should attempt to perform an operation such as loading data like you have suggested. As long as you don't try to do [[NSManager defaultManager] fileExistsAtPath:path] you won't be introducing race conditions.
You don't, and this could introduce a race, so it will be more "ugly". Just check if codedData if nil in case of reading failure.
I am new to ObC and have a problem that i just cant fix. There may be other issues as well but the main issue is this:
Starting the app
Press button = load new view
In the new viewDidLoad i call another object/function and send a NSMutableArray
Process data and send back a NSMutableArray
App crash, see comment where. Most often when i go back and back again but sometimes the first time
As i am new to this i guess i do a lot of this wrong but could someone nice take a look at the code and give me some advice. I would assume i have problem with releasing something.
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(#" ");
NSLog(#"viewDidLoad ");
NSLog(#" ");
NSLog(#">>Processing prepareGame<<");
NSMutableArray *propArray1 = [[NSMutableArray alloc] initWithObjects:#"9999", nil]; //Init with dummy numbers
AccessPropertiesFile *readMyProperties = [AccessPropertiesFile new]; //Init function call to read file
NSLog(#"Prepare to call readProperties");
propArray1 = [readMyProperties readPropertiesFile:propArray1];
NSLog(#"Back from readProperties:error after this");
/*
for (NSString *element in propArray1) {
NSLog(#"Elements in prop2Array; %#", element);
}
*/
[readMyProperties release];
[propArray1 release];
}
-(NSMutableArray *)readPropertiesFile:(NSMutableArray *)readDataArray {
NSLog(#"Processing readProperties");
// For error information
NSError *error;
//Prepare File Manager
NSString *filePath = [self dataFilePath];
NSFileManager *fileMgr;
fileMgr = [NSFileManager defaultManager];
NSArray *propertiesArray = [NSArray alloc]; //Alloc array
//Check from what module the call is coming from to ecide what to do
if ([fileMgr fileExistsAtPath: filePath] == NO) {
NSLog (#"File not found");
//File does not exists, this is the first time the game starts
//Set up default parameters
NSString *fileString =#"0\n30\n30\n10\n1\n1\n1\n2\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n1\n";
// Write default parameters to file
[fileString writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:&error];
propertiesArray = [fileString componentsSeparatedByString:#"\n"]; // each line, adjust character for line endings
}
else { //File exists
NSLog (#"File exists");
NSString *fileString = [NSString stringWithContentsOfFile:filePath
encoding:NSUTF8StringEncoding error:nil]; // reads file into memory as an NSString
propertiesArray = [fileString componentsSeparatedByString:#"\n"]; // each line, adjust character for line endings
}
//Clean readDataArray
[readDataArray removeAllObjects];
//Populate return array
for (NSString *element in propertiesArray) {
//NSLog(#"Elements in propertiesArray; %#", element);
[readDataArray addObject:element];
}
NSLog(#"readDataArray: %#", readDataArray);
[propertiesArray release];
[readDataArray autorelease];
NSLog(#"returning from readProperties");
return readDataArray;
}
#end
You are over-releasing readDataArray (known as propArray1 in the method that didn't create it). You create it and autorelease it in your second method, then you release it again at the end of your first method (where it wasn't created).
I suggest you use Analyze feature that comes with latest XCode. It is a good feature that I always use to track if I forget to release or release too much.
I also spotted that you also over-release the propertiesArray because it contains the result from [fileString componentsSeparatedByString:], which will be autorelease according to Cocoa convention.