Adding an object to an array read from a file - objective-c

There is nothing in the file, but I add row1 to my array right after. NSLog tells me that the array is empty. Why isn't row1 added to the array? All of my other code is fine as far as I can tell. My app worked when I put hard values into the array. Now that I'm loading from a file, it doesn't work.
- (void)viewDidLoad {
//array value
//NSMutableArray *array;
NSString *filePath = [self dataFilePath];
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath])
{
NSMutableArray *array/*array*/ = [[NSMutableArray alloc] initWithContentsOfFile:filePath];
NSDictionary *row1 = [[NSDictionary alloc] initWithObjectsAndKeys:#"Study", #"Task", #"2 hours", #"Length", #"4", #"Hours", #"0", #"Minutes", #"15", #"Tiredness", nil];
[array addObject: row1];
self.tasks = array;
NSLog(#"The contents of the array (file exists) is %#", array);
[array release];
[myTableView reloadData];
}
UIApplication *app = [UIApplication sharedApplication];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(applicationWillTerminate:)
name:UIApplicationWillTerminateNotification
object:app];
[super viewDidLoad];
}
Please help!
Thanks in advance,
Matt

Two possibilities I see:
The object containing this code is not correctly set up as the table's datasource.
A file exists at the path you're looking at, but its contents cannot be parsed as an array. In this case, you would hit the first branch of the if-clause, but initWithContentsOfFile: would return nil. You could easily diagnose this by checking for nil after calling that method.

make sure the tableview delegate and datasource is set properly

It is difficult to answer your question. Here are two suggestions:
When you post code here, clean it up. Remove comments and format it nicely.
Show all your code. In your code there could be many things wrong. Maybe the self.tasks property is not correct. Maybe you did not correctly setup the dataview. Maybe you did not implement the correct table view delegate methods. It is hard to tell.
Also, try ruling out the most basic things. For example if you are in doubt whether that array is setup correctly, then simply print it:
NSLog(#"The contents of the array is %#", array);

Related

CoreData, child MOC on separate thread, unexpected: error: NULL _cd_rawData but the object is not being turned into a fault

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];
}
}

Getting valueForKey returns (null)

I am trying to put a value in a NSArray and receive it again later. Here is my code:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[_system setValue:[_objects objectAtIndex:indexPath.row] forKey:#"selected"];
NSLog(#"%#", [_objects objectAtIndex:indexPath.row]);
[tableView deselectRowAtIndexPath:indexPath animated:YES];
NSLog(#"%#", [_system valueForKey:#"selected"]);
}
Here is the log result:
2013-11-01 23:38:04.210 ClassPoints[187:60b] test
2013-11-01 23:38:04.211 ClassPoints[187:60b] (null)
What I find odd is even creating the array in the void doesn't properly load the value.
NSArray *testArray;
[testArray setValue:#"test" forKey:#"test"];
NSLog(#"%#", [testArray valueForKey:#"test"]);
Could anyone shed some light on this? I am completely lost. Thanks!
Make your NSArray an NSMutableArray so you can edit objects in it and in your viewDidLoad do not forget to initialize it.
-(void)viewDidLoad {
myMutableArray = [[NSMutableArray alloc] init];
}
Once an NSArray has been initialized, you won't be able to add/remove/edit objects on it. That is what an NSMutableArray is for.
First you need to use the mutable versions of the collection type you want to use, then you need to alloc and init them. Then unless your _system (NSArray?) contains objects that are key value coding compliant for "selected". setValue:forKey calls setValue:forKey on each of your arrays elements. The same applies for valueForKey:. Thats why your isolated example wont work either. I think you want a NSMutableDictionary then you can get/set objects through keys while arrays only works with indexes.
NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] init];
[dictionary setObject:yourObject forKey:yourKey];
id obj = [dictionary objectForKey:yourKey];
Try like this use NSMutableArray and for setting use setObject api inspite of setting setValue.
NSMutableArray *testArray = [NSMutableArray array];
[testArray setObject:#"test" forKey:#"test"];
NSLog(#"%#", [testArray objectForKey:#"test"]);

Change from a NSMutableArray to a .plist

I have been working through several tutorials on uitableviews.
I have put, as instructed, all the info into a 'listofitems' as below
listOfItems = [[NSMutableArray alloc] init];
NSArray *countriesToLiveInArray = [NSArray arrayWithObjects:#"Iceland", #"Greenland", #"Switzerland", #"Norway", #"New Zealand", #"Greece", #"Rome", #"Ireland", nil];
NSDictionary *countriesToLiveInDict = [NSDictionary dictionaryWithObject:countriesToLiveInArray forKey:#"Countries"];
NSArray *countriesLivedInArray = [NSArray arrayWithObjects:#"India", #"U.S.A", nil];
NSDictionary *countriesLivedInDict = [NSDictionary dictionaryWithObject:countriesLivedInArray forKey:#"Countries"];
[listOfItems addObject:countriesToLiveInDict];
[listOfItems addObject:countriesLivedInDict];
This creates a sectioned table view. I would like to know how to change it into a .plist instead of typing it all out into the RootViewController.m. I would still like it to be in a sectioned tableview.
Is there a simple method for changing from this NSMutableArray,NSArray and NSDictionary to a plist?
There's a simple method for this writeToFile:atomically::
[listOfItems writeToFile:destinationPath atomically:YES];
This will automatically create a file with plist inside it.
that sorta depends on what you want in a plist, and what you put into it. if the entries and contents are all CFPropertyList types (CFString,CFDate,CFData,CFDictionary,CFArray,CFNumber...) then just create it with something like CFPropertyListCreateDeepCopy.
if you have non-convertible custom objects (e.g., your own NSObject subclasses), then see the cocoa archiving topics.
This is the simple function end hear relization
This is function is updating NSArray
- (void) WriteRecordToFile:(NSMutableDictionary*)countDict {
// Here to write to file
databasePathCallCount = #"plist path";
NSMutableArray *countArray = [NSMutableArray arrayWithContentsOfFile:databasePathCallCount];
if(countArray)
[countArray addObject:countDict];
[countArray writeToFile:databasePathCallCount atomically:NO];
}

EXC_BAD_ACCESS when returning UIImage

I have a method which should return a UIImage created from contentsOfFile (to avoid caching), but when it returns, i receive EXC_BAD_ACCESS. Running through Instruments doesn't reveal any results, as it just runs, without stopping on a zombie.
The image is correctly copied in the Bundle Resources phase...
- (UIImage *)imageForName:(NSString *)name {
NSString *path = [[NSBundle mainBundle] pathForResource:name ofType:#"png"];
return [UIImage imageWithContentsOfFile:path];
}
This method was adapted from the PhotoScroller sample, which works correctly.
Thanks
EDIT:
This is the code that uses imageForName, and you can see i added the retain, as per Luke/T's suggestion, but the EXC_BAD_ACCESS is on the return, not my addObject: call:
NSMutableArray *images;
for (NSDictionary *dict in imageListForPage){
[images addObject:[[self imageForName:[dict valueForKey:#"name"]]retain]];
}
ImageWithContentsOfFile will return an auto-released object. If you are not retaining it (on return [edit]) then you will get a bad access.
Edit:
Check the pointer of the NSarray. You need to init the Array either alloc as normal or use the arraywith
e.g.
NSMutableArray *images = [NSMutableArray arrayWithCapacity:ARRAY_CAPACITY];//autoreleased
or
NSMutableArray *images = [[NSMutableArray alloc] init];//release later
Adding an object to an NSMutableArray will implicitly send it a retain, so that's not necessary in your code.
Can you confirm (using NSLog() or a breakpoint) that
[UIImage imageWithContentsOfFile:path]
returns an object in your imageForName: method?
Finally, this code should be:
NSMutableArray *images = [NSMutableArray new]; // new is same as [[alloc] init]
for (NSDictionary *dict in imageListForPage) {
[images addObject:[self imageForName:[dict valueForKey:#"name"]]];
}
// ... do something with images
[images release];

initWithCoder not working as expected?

Does this seem right, the dataFilePath is on disk and contains the right data, but the MSMutable array does not contain any objects after the initWithCoder? I am probably just missing something, but I wanted to quickly check here before moving on.
-(id)initWithCoder:(NSCoder *)decoder {
self = [super init];
if(self) {
[self setReactorCore:[decoder decodeObjectForKey:#"CORE"]];
}
return self;
}
.
-(id)init {
self = [super init];
if(self) {
if([[NSFileManager defaultManager] fileExistsAtPath:[self dataFilePath]]) {
NSMutableData *data = [[NSMutableData alloc] initWithContentsOfFile:[self dataFilePath]];
NSKeyedUnarchiver *unArchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
NSMutableArray *newCore = [[NSMutableArray alloc] initWithCoder:unArchiver];
[self setReactorCore:newCore];
[newCore release];
[data release];
[unArchiver release];
} else {
NSMutableArray *newCore = [[NSMutableArray alloc] init];
[self setReactorCore:newCore];
[newCore release];
}
}
return self;
}
EDIT_001
I think I know where I am going wrong, I am archiving NSData and then trying to initialise my NSMutable array with it. I will rework the code and post back with an update.
gary
I am confused as to why you're doing things this way. You do not normally call initWithCoder: yourself. You ask the coder for its contents and it creates the objects for you. The whole decoding part of that method should be id archivedObject = [NSKeyedUnarchiver unarchiveObjectWithFile:[self dataFilePath]], where archivedObject is presumably the array you call newCore in your code (I don't know the contents of the file, so I'm just guessing from what you wrote). In that case, you'll want to mutableCopy it, since I don't think NS*Archiver preserves mutability.
I also hope you aren't expecting your initWithCoder: method that you wrote at the top of your post to be called when this NSArray is unarchived.
"I also hope you aren't expecting your initWithCoder: method that you wrote at the top of your post to be called when this NSArray is unarchived."
is not useful. Why can't you write why it's not called? Thanks
EDIT:
In fact, if the objects in your array implement NSCoding, initWithCoding is called on each of them when you restore your array (restore here means that you call [decoder decodeObjectForKey:#"yourArray"]). I'm actually doing this in my own code. So I think what you wrote is false!