I have two classes - BNRItem and BNRContainer. BNRContainer is a subclass of BNRItem. In order to cut down the amount of code I paste, assume the following that I have tested and know works:
+(BNRItem * ) randomItem; // allocate and init a random item.
#property(nonatomic, readwrite, copy) NSMutableArray * subitems; // This is a property of BNRContainer class
main.m:
NSMutableArray * rand_items = [NSMutableArray alloc] init];
for (int i = 0; i < 10; i++) {
[rand_items addObject: [BNRItem randomItem]];
}
[rand_items addObject: #"HELLO"];
BNRContainer * rand_container_of_items = [BNRContainer randomItem];
rand_container_of_items.subitems = rand_items;
[rand_container_of_items.subitems addObject: #"THERE"]; // ERROR SIGABRT
NSLog(#"------------------------------------------------------");
NSLog(#"%#", rand_container_of_items);
rand_container_of_items = nil;
If I NSLog without adding #"THERE", I see "HELLO" in my description so I know that I am able to call addObject: at that point. Why do I get SIGABRT when I am trying to access ivar "subitems" of rand_container_of_items? I just can't figure this one out.
The problem seems to be the copy modifier in your declaration.
#property (nonatomic, readwrite, copy) NSMutableArray *subitems;
In the documentation, the NSCopying protocol conformance is inherited form NSArray, so my suspicion is that in this line
rand_container_of_items.subitems = rand_items;
subitems contains an immutable copy of the original array. Try removing copy from your declaration. If you need a copy use the mutableCopy method.
Problem lies here
property(nonatomic, readwrite, copy) NSMutableArray * subitems;
You should not use copy here since it will return immutable copy of the object. So that you cannot add objects to it. It could be
property(nonatomic, strong) NSMutableArray * subitems;
This line is giving sigbart as when you allocate an array to mutable array, it becomes mutable.
So, when you are copying rand_items to rand_container_of_items.subitem, it is becoming mutable.
So, to make it immutable, try following :
BNRContainer * rand_container_of_items = [BNRContainer randomItem];
rand_container_of_items.subitems = [rand_items mutablecopy];
[rand_container_of_items.subitems addObject:#"THERE"]; // ERROR SIGABRT
Related
When I assign one array to another, I get an error, as shown in P1.
Then I checked the contents of _carIDArray and found an empty object in it. It was this object that caused the crash.P2
Why do empty objects appear in arrays? When using addobjcet: will not the array crash?
Here's some codes related to _carIDArray:
CarModel *car = [[CarModel alloc] initWithStatusAndMsgWithDic:object];
if (!_carIdArray) {
_carIdArray = [NSMutableArray array];
}
if (car.carIdStr && car.carIdStr.length>0){
[_carIdArray addObject:car.carIdStr];
}
*object is the data from the server.
#interface CarModel : NSObject
#property (nonatomic,copy) NSString *carIdStr;
#end
I have copied some code that works fine for saving an api to one entity in core data over to save a similar api to another entity. Although everything is largely the same, I cannot get rid of exception error.
Here is the code that throws the exception:
- (NSMutableArray *) convertFeedtoObject:(NSMutableArray*)feed {
NSMutableArray * _newitems = [[NSMutableArray alloc] init];
for (int i = 0; i < feed.count; i++) {
NSDictionary *feedElement = feed[i];
ItemOnServer *newItem = [[ItemOnServer alloc] init];
newItem.itemtitle = feedElement[#"itemtitle"]; //throws exception
newItem.item = feedElement[#"item"];//also throws an exception if above commented out
newItem.descript = #"some item"; //if above lines commented out, littoral throws exception too.
//same goes for any other values I try to set. The first one throws exception.
Here is the error:
2015-11-20 05:26:03.281 [20610:60b] -[ItemOnServer setItem:]: unrecognized selector sent to instance 0x175721b0
No matter what values I try to set, I get a similar error even though I know the values are there.
One item of the feed looks like this:
{
lastviewed = "2015-11-17 15:21:45";
itemtitle = "New";
id = 944;
item = "cotton shirt";
}
)
Thanks for any suggestions.
Edit:
Code from ItemOnServer.h
//.m file is largely empty
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
#class ItemsVC;
#class Vendors;
#interface ItemOnServer : NSObject
#property (nonatomic, retain) NSString * item;
#property (nonatomic, retain) NSNumber * id;//this is permanent id shared with server
#property (nonatomic, retain) NSNumber * localid; //this is temp id if first created locally
#property (nonatomic, retain) NSString * itemtitle;
#property (nonatomic, retain) NSDate * lastviewed;
//this is relationship with vendors
#property (nonatomic, retain) Vendors *vendor;
#end
Instead of using an NSDictionary declare feedElement as an NSMutableArray, so something like:
- (NSMutableArray *) convertFeedtoObject:(NSMutableArray*)feed {
NSMutableArray * _newitems = [[NSMutableArray alloc] init];
NSMutableArray *feedElement = [[NSMutableArray alloc] init];
for (int i = 0; i < feed.count; i++) {
feedElement = feed[i];
ItemOnServer *newItem = [[ItemOnServer alloc] init];
newItem.itemtitle = feedElement[#"itemtitle"];
...
It looks like your class ItemOnServer doesn't have implementations for the methods setItem:, setItemtitle and so on. I don't know how you managed to do this, but you should look at the interface and implementation of ItemOnServer.
Look at what the error message says:
-[ItemOnServer setItem:]: unrecognized selector
That means a setItem: message is sent to an object, that object is an ItemOnServer object, and it doesn't implement setItem: The first two of these three points are exactly what you expect to happen when you write newItem.item = ...
Do you have another class named ItemOnServer in your application, maybe linked through some library? You can easily check by renaming ItemOnServer with ItemOnServer2 and checking what happens.
I declare the following in ViewController.h
#property (nonatomic, strong) NSMutableArray *locations;
and the following in ViewController.m
#implementation GHViewController
#synthesize locations;
...
for (FSFourSquareVenueObject *object in array) {
locations = [[NSMutableArray alloc] init];
[locations addObject:object.locationName];
NSLog(#"%#", locations);
}
This successfully logs all the string locations that have been placed in the locations NSMutableArray. How can I access this NSMutableArray in a different class?
I am trying to access it in my TableViewController class in order to display all the elements in the array. I have tried importing the ViewController.h file into my TableViewController.h file, yet I still cannot access the array from the ViewController class.
Remove the line
locations = [[NSMutableArray alloc] init];
from your for loop and place it somewhere like viewDidLoad or init. You're wiping out your array every time before adding a new object.
To access a single object across classes, you want to look into creating a singleton. There are many tutorials online.
Do as #Stonz2 suggests, but also modify your headers as follows:
In GHViewController.h:
#property (nonatomic, strong) NSArray *locations;
In GHViewController.m
#implementation GHViewController
#synthesize locations;
...
NSMutableArray *array = [[NSMutableArray alloc] init];
for (FSFourSquareVenueObject *object in array) {
[array addObject:object.locationName];
NSLog(#"%#", array);
}
self.locations = [array copy];
You can then access the array from another class using GHViewController -locations. You can edit the locations using the following snippet (or by creating a similar method in GHViewController):
NSMutableArray *array = [gh_viewController.locations mutableCopy];
[array addObject: newLocation];
gh_viewController.locations = [array copy];
Exposing a mutable array allows other class to modify the array without notifying GHViewController and vice versa. This can lead to unpredictable and hard to debug problems, such as if GHViewController removes some elements while TableViewController is iterating through all the objects. Using a non-mutable array prevents these sorts of bugs and ensures everyone has a consistent view of what's inside.
My problem is that I have a class property that is of type NSMutableArray, as defined in my header file, yet when I attempt to modify one of the array elements (an NSDictionary) I receive the following runtime error:
2013-01-16 14:17:20.993 debtaculous[5674:c07] * Terminating app due
to uncaught exception 'NSInternalInconsistencyException', reason:
'-[__NSCFArray replaceObjectAtIndex:withObject:]: mutating method sent
to immutable object'
Header declaration:
// BudgetViewController.h
#import <UIKit/UIKit.h>
#interface BudgetViewController : UIViewController <UITableViewDataSource, UITableViewDelegate>
- (IBAction)afterTaxIncomeEditingDidEnd:(id)sender;
#property (strong, nonatomic) NSMutableArray *budgetArray;
#property (strong, nonatomic) IBOutlet UITextField *afterTaxIncome;
#property (strong, nonatomic) IBOutlet UITableView *budgetTableView;
#end
Method that generates the error:
-(void)applyCCCSWeights
{
NSMutableDictionary *valueDict;
NSString *newAmount;
for (id budgetElement in [self budgetArray]) {
valueDict = [[NSMutableDictionary alloc] initWithDictionary:budgetElement];
newAmount = [NSString stringWithFormat:#"%0.2f", [[self afterTaxIncome].text floatValue] * [[budgetElement objectForKey:#"cccs_weight"] floatValue]];
[valueDict setValue:newAmount forKeyPath:#"amount"];
[[self budgetArray] replaceObjectAtIndex:0 withObject:valueDict];
NSLog(#"%0.2f (%0.2f)", [[budgetElement objectForKey:#"amount"] floatValue], [[self afterTaxIncome].text floatValue] * [[budgetElement objectForKey:#"cccs_weight"] floatValue]);
}
[self.budgetTableView reloadData];
}
// Note the replaceObjectAtIndex:0 above is just a placeholder. This will be replaced with the correct index.
budgetArray is surely immutable, you have to create it mutable.
Probably you're doing something like this:
budgetArray= [NSArray arraWithObjects; obj1, obj2, nil];
And ignoring the compiler warning. Make it mutable:
budgetArray= [[NSMutableArray alloc]init];
I'm fairly certain you cannot change a mutable object during enumeration.
This SO question may help: Setting an object during fast enumeration problems
In your init method, put this:
budgetArray = [[NSMutableArray alloc] init];
Also, why not use dictionary and array literal syntax?
-(void)applyCCCSWeights {
NSMutableDictionary *valueDict;
NSString *newAmount;
for (NSDictionary *budgetElement in [self budgetArray]) {
valueDict = [budgetElement mutableCopy];
newAmount = [NSString stringWithFormat:#"%0.2f", [[self afterTaxIncome].text floatValue] * [budgetElement[#"cccs_weight"] floatValue]];
valueDict[#"amount"] = newAmount;
_budgetArray[0] = valueDict;
NSLog(#"%0.2f (%0.2f)", [budgetElement[#"amount"] floatValue], [[self afterTaxIncome].text floatValue] * [budgetElement[#"cccs_weight"] floatValue]);
}
[self.budgetTableView reloadData];
}
Notice that [[self budgetArray] replaceObjectAtIndex:0 withObject:valueDict];
becomes: _budgetArray[0] = valueDict;
You can't change an array while doing a fast iteration over the array. On the other hand, it's entirely unnecessary; the code is absolutely inefficient: Just make the elements of the array NSMutableDictionaries, then change the dictionaries directly, instead of creating a copy and then changing an element in the copy.
Noticed later that you use NSJSONSerialization; look at the flags and don't pass 0 blindly.
I have looked at numerous posts which state various ways in which to remove an object from an array correctly, but I am not sure which method is best to use in my instance. I am loading a dictionary from a plist, this dictionary contains numerous arrays, and these arrays contain another dictionary. So I have 3 storage devices setup, 1 to hold the overall dictionary, another for an array, and finally a dictionary to hold the object from the array:
Header:
NSMutableDictionary *questionsDictionary;
NSMutableArray *questionsArray;
NSDictionary *currentQuestion;
#property (nonatomic, retain) NSMutableDictionary *questionsDictionary;
#property (nonatomic, retain) NSMutableArray *questionsArray;
#property (nonatomic, retain) NSDictionary *currentQuestion;
So my first question is to do with the above, are (nonatomic, retain) the right things to use for the following code.
Next I load in my dictionary from the plist:
.m:
NSString *path = [[NSBundle mainBundle] bundlePath];
NSString *finalPath = [path stringByAppendingPathComponent:#"MultipleChoice.plist"];
self.questionsDictionary = [[NSDictionary dictionaryWithContentsOfFile:finalPath] retain];
I then setup my question array based upon type based upon my question type:
- (void)setupQuestionType : (NSString *)qType
{
if ([self.questionsDictionary objectForKey:qType])
{
self.questionsArray = [self.questionsDictionary objectForKey:qType];
[self pickRandomQuestion];
}
}
Finally (this is where I get the error), I want to grab the a question at random from this question category:
// Pick a random question number based upon amount of questions
int randomQuestionNum = [[NSNumber numberWithInt:(arc4random() % [self.questionsArray count])] intValue];
// Grab the dictionary entry for that question
currentQuestion = [self.questionsArray objectAtIndex:randomQuestionNum];
// Remove the question from the available questions
[self.questionsArray removeObjectAtIndex:randomQuestionNum]; (Error here)
// Set the question text
self.question.text = [currentQuestion objectForKey:kQuestionkey];
Now if I comment out the removeObjectAtIndex line then the code runs fine, and my question is displayed on the screen. This leads me to believe that it isn't a null pointer. So the logical answer points to the fact that self.questionsArray isn't a NSMutableArray. So I tried the following when setting the array:
- (void)setupQuestionType : (NSString *)qType
{
if ([self.questionsDictionary objectForKey:qType])
{
NSMutableArray *temp = (NSMutableArray *)[self.questionsDictionary objectForKey:qType];
self.questionsArray = (NSMutableArray *)temp;
[self pickRandomQuestion];
}
}
Purely to see if I could type_cast it but the error still occurs. Can anyone shed some light on what I'm doing wrong, or the best approach to take?
Don't typecast NSArray to NSMutableArray. Instead:
NSArray *temp = [self.questionsDictionary objectForKey:qType];
self.questionsArray = [NSMutableArray arrayWithArray:temp];
// code not tested.