I am trying to remove a pointer in an NSMutableArray that points to an object in another array without deleting the object itself. E.g.:
// In ViewController.m – Code abridged and somewhat simplified
#interface ViewController ()
#property (nonatomic, strong) NSMutableArray *objectPool;
#property (nonatomic, strong) NSMutableArray *objectsOwnedByFriend;
#property (nonatomic, strong) NSMutableArray *objectsOwnedByMe;
- (void)transferPointerToObjectFromFriendToMe;
- (void)copyPointerToObjectFromFriendToMe;
#end
#implementation ViewController
#synthesize objectPool = _objectPool;
#synthesize objectsOwnedByFriend = _objectsOwnedByFriend;
#synthesize objectsOwnedByMe = _objectsOwnedByMe;
- (void)setObjectPool:(NSMutableArray *)objectPool
{
_objectPool = objectPool;
}
- (NSMutableArray *)objectPool
{
if (!_objectPool) _objectPool = [[NSMutableArray alloc] initWithArray:self.objects]; // self.objects is a mutable array containing multiple NSObjects
return _objectPool;
}
- (void)setObjectsOwnedByFriend:(NSMutableArray *)objectsOwnedByFriend
{
_objectsOwnedByFriend = objectsOwnedByFriend;
}
- (NSMutableArray *)objectsOwnedByFriend
{
if (!_objectsOwnedByFriend)
{
_objectsOwnedByFriend = [[NSMutableArray alloc] init];
[_objectsOwnedByFriend addObjectsFromArray:self.objectPool];
}
return _objectsOwnedByFriend;
}
- (void)setObjectsOwnedByMe:(NSMutableArray *)objectsOwnedByMe
{
_objectsOwnedByMe = objectsOwnedByMe;
}
- (NSMutableArray *)objectsOwnedByMe
{
if (!_objectsOwnedByMe) _objectsOwnedByMe = [[NSMutableArray alloc] init];
return _objectsOwnedByMe;
}
- (void)transferPointerToObjectFromFriendToMe
{
[self.objectsOwnedByMe addObject:[self.objectsOwnedByFriend lastObject]];
[self.objectsOwnedByFriend removeLastObject];
}
- (void)copyPointerToObjectFromFriendToMe
{
[self.objectsOwnedByMe addObject:[self.objectsOwnedByFriend lastObject]];
}
#end
In the above code, when I use transferPointerToObjectFromFriendToMe, removing the last object removes both the pointer to it in self.objectsOwnedByFriend (as I want) and also the object itself in self.objectPool (which I don't want to happen).
What I would like is an array (self.objectPool) that contains all of the actual objects and then two mutable arrays (self.objectsOwnedByFriend and self.objectsOwnedByMe) that contains pointers to objects in self.objectPool and the ability to add and remove more pointers referencing objects in self.objectPool to self.objectsOwnedByFriend and self.objectsOwnedByMe.
Also, when I use either transferPointerToObjectFromFriendToMe or copyPointerToObjectFromFriendToMe, the object doesn't seem to be added properly, as a subsequent check via self.objectsOwnedByMe.count results in 0 instead of 1.SOLUTION = My lazy instantiation for self.objectsOwnedByMe was missing in my original code :SI was able to check whether self.objectsOwnedByMe was properly created via:
NSLog(#"self.objectsOwnedByMe = %#", self.objectsOwnedByMe);
** My first StackOverflow question! ** I hope I was clear...couldn't find a a similar question so apologies if I missed an old thread. Let me know if you need more info to diagnose. (I am trying to learn Obj-C.)
Typo :P Sorry peeps. In my actual code in Xcode I had:
- (void)setObjectPool:(NSMutableArray *)objectPool
{
_objectPool = objectPool;
}
- (NSMutableArray *)objectPool
{
if (!_objectPool) _objectPool = [[NSMutableArray alloc] initWithArray:self.objects];
return _objectsOwnedByFriend;
}
I think my mistake is super obvious (and if not, the mistake was that my getter for objectPool was returning _objectsOwnedByFriend...copy/paste error that I somehow missed).
Everything works now!
This is very peculiar and confusing code. I suspect the problem is that something is calling one of the setters, -setObjectPool: or -setObjectsOwnedByFriend:, with the array of the other object. Those setters simply make the ivar refer to the object that was passed in. Because of that, they are very prone to lead to objects being shared.
Typically, a property like that would be declared and implemented with copy semantics.
It looks like self.objectsOwnedByMe is never initialized and you are therefore always working with nil instead of an actual NSMutableArray.
Somewhere (perhaps in a custom getter for objectsOwnedByMe as below?) you need to create an array before you start using it:
- (NSMutableArray *)objectsOwnedByMe {
if (_objectsOwnedByMe == nil) {
_objectsOwnedByMe = [[NSMutableArray alloc] init];
}
return _objectsOwnedByMe;
}
This would explain both problems: Since it is nil it never retains the objects and therefore they go away when removed from the other array, and also why they are never added to the _objectsOwnedByMe array.
Related
I'm new to Objective C and iOS development in general, so if I missed some information that needs to be included please let me know and I'll do my best. At the same time, if any of my assumptions are incorrect or if I set this up totally dumb, please don't hesitate to yell at me. Thanks!
I have a class object called feeds. Here's how I initialize it in the .h file:
#interface ClassViewController : ContentViewController <UITableViewDelegate,
UITableViewDataSource> {
NSMutableArray *feeds;
}
#property (nonatomic, strong) NSMutableArray* feeds;
and in the .m:
#dynamic feeds;
I'm trying to get results from JSON and load them up into the feeds. Let's assume that resArr has correct data in it:
NSArray *resArr = [results objectForKey:#"data"];
if([self->feeds count]) {
[self->feeds removeAllObjects];
[self->feeds addObjectsFromArray:resArr];
}
else {
self->feeds = [[NSMutableArray alloc] initWithArray:resArr];
}
Now, this works fine the first time (i.e., the first time I put data into the array), but I get the following error subsequent times: *** -[NSMutableArray addObjectsFromArray:]: array argument is not an NSArray
I'm clueless. Any ideas?
Edit: JSON structures
1:
{"code":200,"data":[{"name":"ABM"},{"name":"ACC"}]}
2:
{"code":200,"data":{"100":{"subject":"ABM","title":"Decision Making in Agri-Food"},"130":{"subject":"ABM","title":"Farm Management I"}}}
you should use self.feeds instead of self->feeds, here's a good read Dot (“.”) operator and arrow (“->”) operator use in C vs. Objective-C
are you sure the subsequent times are actually NSArrays?
edit additon
Looks like the first JSon is just a array of names, where's as the second is a dictionary keyed with 100, 200, etc. So the JSON converts this into a dictionary keyed to 100, 200, ...
NSArray* resArr = [results objectForKey:#"data"];
if([self.feeds count]) {
[self.feeds removeAllObjects];
if([resArr isKindOfClass:[NSDictionary class]])
[self.feeds addObjectsFromArray:resArr.allValues];
else
[self.feeds addObjectsFromArray:resArr];
}
else {
self.feeds = [[NSMutableArray alloc] initWithArray:resArr];
}
I have a method that creates a dictionary from NSJSONSerialization class. I then enumerate the json, and create objects to store state for each instance.
- (void)fetchedData:(NSData *)responseData {
NSError* error;
NSDictionary *json = [NSJSONSerialization
JSONObjectWithData:responseData
options:kNilOptions
error:&error];
NSArray *moviesJson = [json objectForKey:#"movies"];
for(NSDictionary *element in moviesJson)
{
RABMovie *myMovie = [[RABMovie alloc] initWithJson:element];
// RABMovie *myMovie = [RABMovie new];
// myMovie.json = element;
[_myMovieNames addObject: myMovie];
myMovie = nil;
}
[self.movieTableView reloadData];
}
Problem: I want to create my object by passing in element in the allocator, however when I do this, my UITTableView rows all contain the same movie data. It is always the last item, leading me to believe I am working with only one memory address, and the last update affects the whole collection.
If I uncomment the code to use the json as a property instead of a alloc param, I no longer have the issue and all works well. I've also tried creating a completely new NSDictionary via a deep copy of element to no avail.
Does someone know why this is happening? BTW, I am using ARC. -Thanks for the time.
Edit: Added more code. I've included a property movieName to illustrate how I use the ivar _json.
#implementation RABMovie
NSDictionary *_json;
- (id) initWithJson: (NSDictionary*) jsonAsDictionary
{
if (self = [super init])
{
_json = jsonAsDictionary;
}
return self;
}
- (NSString*) movieName
{
return [_json objectForKey:#"title"];
}
I think you meant to declare _json as an instance variable. Instead it's a globally visible (at least within that class) variable - not 100% sure on the scoping rules, but regardless, it's not an instance variable - it's a single variable shared by all instances! Try this instead:
#implementation RABMovie {
NSDictionary *_json;
}
/* ...rest of class */
#end
Putting it inside the curly braces after the #implementation directive makes it an instance variable. Hope this helps!
EDIT: Do you have a property called json on RABMovie already? Then you can skip the instance declaration altogether and the compiler will generate the an instance variable for you. That's probably happening already actually, which is why it works when you go through the property - it's accessing the ivar rather than the "global".
As a relative Objective-C beginner, I'm obviously still not grasping certain memory management rules. I can't figure out how to make this not crash:
#interface MyClass { NSArray *playerArray4th; }
- (void) viewDidLoad { playerArray4th = [self getAudioPlayersForSoundFile:#"rimshot" ofType:#"aif"]; }
- (NSArray*) getAudioPlayersForSoundFile:(NSString*)soundFileName ofType:(NSString*)soundFileType {
//code instantiating objects....
NSArray *toRet = [[NSArray alloc] initWithObjects:toRetTickPlayer,toRetTickPlayerCopy,toRetTickPlayerCopy2,toRetTickPlayerCopy3, nil];
return toRet;
}
Then later, in a different function:
NSArray *currentArray = playerArray4th;
[currentArray release];
currentArray = nil;
currentArray = [self getAudioPlayersForSoundFile:fileName ofType:ofType];
And it crashes when trying to access the array again:
- (void) playSound:(NSString*)soundType {
AVAudioPlayer *currentPlayer;
if ([soundType isEqualToString:#"4th"]) {
if (playerArray4thCounter >= [playerArray4th count]) playerArray4thCounter = 0;
NSLog(#"Playing from array: %#",playerArray4th);
currentPlayer = [playerArray4th objectAtIndex:playerArray4thCounter];
playerArray4thCounter++;
}
}
Try to learn about properties and about using getters and setters. Don't take shortcuts unless you know exactly what's going on.
So define the playerArray4th property in your header file:
#property (nonatomic,retain) NSArray *playerArray4th;
And then in your .m file create getter/setter:
#synthesize playerArray4th;
Then, always use self.playerArray4th for assigning and getting the variable. The prior objects will be released when needed.
So this will not leak:
self.playerArray4th = [NSArray arrayWithObjects:#"text",#"text",nil];
self.playerArray4th = [NSArray arrayWithObjects:#"new array",#"text",nil];
because the second assignment releases the first array.
Furthermore, read about using autorelease. In short, if you alloc, copy or new, you should either release or autorelease. There's a lot to read about this here on SO which I will not repeat here now.
Don't forget to put self.playerArray4th = nil; in your dealloc method.
I've been reading about NSArrays and NSDictionaires and I think I need the later. I'm trying to populate an object from a small database table. So I can access the string values via a record id. I have to do this several times so putting it into an object makes sense.
I have the basics...
- (void)viewDidLoad {
// WORKING START
NSMutableDictionary *dictCategories = [[NSMutableDictionary alloc] init];
[dictCategories setValue:#"Utility" forKey:#"3"];
[dictCategories setValue:#"Cash" forKey:#"5"];
NSString *result;
result = [dictCategories objectForKey:#"3"];
NSLog(#"Result=%#", result);
// WORKING END
// Can't get this bit right, current error Request for member
// 'getCategories' in something not a structure or union
NSMutableDictionary *dictCategories2 = self.getCategories;
NSLog(#"Result2=%#", [dictCategories2 objectForKey:#"5"]);
[super viewDidLoad];
}
-(NSMutableDictionary*)getCategories {
NSMutableDictionary *dictCategories = [[NSMutableDictionary alloc] init];
[dictCategories setValue:#"Utility" forKey:#"3"];
[dictCategories setValue:#"Cash" forKey:#"5"];
return dictCategories;
}
you are calling the method wrong,try [self getCategories]
You're not being clear on what isn't working, but a few things that are obviously wrong (JonLOo might be spot on though) ...
Firstly. You're using the wrong methods, or at least there's a better one -- setValue:forKey: should/could be setObject:forKey: instead. This might be one of the reasons for your issue.
Secondly. You're over-allocating and not releasing properly. dictCategories2 in your viewDidLoad will vanish into the void and bring with it the allocated memory for dictCategories defined in the getCategories method. An easy standard fix for this is to change
NSMutableDictionary *dictCategories = [[NSMutableDictionary alloc] init];
in getCategories into
NSMutableDictionary *dictCategories = [NSMutableDictionary dictionary];
It will be autoreleased using the latter method by the system.
Thirdly. You want to read up on #property. Instead of getFoo, setBar, the Ob-C standard is to use #properties to (pre)define setters and getter methods. You can then override these to populate default data into your methods when appropriate. You also (probably) want to store the dictionary in your interface as an instance variable, rather than letting it be deallocated all the time. Example of a #property implementation that does this:
#interface foo {
NSMutableDictionary *ingredients;
}
#property (nonatomic, retain) NSMutableDictionary *ingredients;
#end
// ....
#implementation foo
#synthesize ingredients;
// ...
// the #synthesize command above will create getter and setter methods for us but
// we can override them, which we need to do here
- (NSMutableDictionary *)ingredients
{
if (ingredients != nil) {
// we've already got an ingredients variable so we just return it
return ingredients;
}
// we need to create ingredients
ingredients = [[NSMutableDictionary alloc] init];
[ingredients setObject:#"foo" forKey:#"bar"]
return ingredients;
}
In the viewDidLoad method (or anywhere else where you think ingredients might not have been initialized yet), you would do e.g.
NSMutableDictionary *dict = self.ingredients;
Anywhere else you can opt to use just ingredients without self, but if it's nil, your method will never be called, and you will get nil thrown at you.
This is useful in many cases, and is necessary if we want to ever read or write the ingredients variable from outside of our class. It's outside of what you're asking about, but I brought it up because you're trying to do something similar with self.getCategories.
Hope that helps.
-(void)setUserFilters{
//init the user filters array
userFilters = [[NSMutableArray alloc] init];
SearchCriteria *tmpSc= [[SearchCriteria alloc] init];
for(int i=0;i<[searchFilters count];i++)
{
tmpSc=[self.searchFilters objectAtIndex:i];
if(tmpSc.enabled==TRUE)
[userFilters addObject:tmpSc];
}
}
searchFilters is a list of filters that can be setted to true or false and I use userFilters to populate a table view with the filters that are only setted to TRUE
But the line SearchCriteria *tmpSc= [[SearchCriteria alloc] init]; causes leaks, and I don't know how to solve because if I release at the end of the function I loose my pointers and it crashes
Any ideas?
twolfe18 has made the code >much slower if searchFilters can be large. -objectAtIndex: is not a fast operation on large arrays, so you shouldn't do it more than you have to. (While true that FE is faster than objectAtIndex:, this overstated the issue and so I've striken it; see my other comments on the advantages of Fast Enumeration.)
There are a number of problems in your code:
Never create a method that begins "set" but is not an accessor. This can lead to very surprising bugs because of how Objective-C provides Key-Value Compliance. Names matter. A property named userFilters should have a getter called -userFilters and a setter called -setUserFilters:. The setter should take the same type that the getter returns. So this method is better called -updateUserFilters to avoid this issue (and to more correctly indicate what it does).
Always use accessors. They will save you all kinds of memory management problems. Your current code will leak the entire array if -setUserFilters is called twice.
Both comments are correct that you don't need to allocate a temporary here. In fact, your best solution is to use Fast Enumeration, which is both very fast and very memory efficient (and the easiest to code).
Pulling it all together, here's what you want to be doing (at least one way to do it, there are many other good solutions, but this one is very simple to understand):
#interface MyObject ()
#property (nonatomic, readwrite, retain) NSMutableArray *userFilters;
#property (nonatomic, readwrite, retain) NSMutableArray *searchFilters;
#end
#implementation MyObject
#synthesize userFilters;
#synthesize searchFilters;
- (void)dealloc
{
[searchFilters release];
serachFilters = nil;
[userFilters release];
userFilters = nil;
[super dealloc];
}
- (void)updateUserFilters
{
//init the user filters array
// The accessor will retain for us and will release the old value if
// we're called a second time
self.userFilters = [NSMutableArray array];
// This is Fast Enumeration
for (SearchCriteria *sc in self.searchFilters)
{
if(sc.enabled)
{
[self.userFilters addObject:sc];
}
}
}
It seems that your initially creating a SearchCriteria object and before you use it or release it your reassigning the variable to another object from self.searchFilters. So you don't need to create the initial object and why it's leaking and not being released.
Try:
SearchCriteria *tmpSc = nil;
Hope that helps.
first of all, the worst n00b code you can write involves if(condition==true) do_something(), just write if(condition) do_something().
second, there is no reason to have tempSc at all (never mind alloc memory for it), you can just do the following:
-(void)setUserFilters{
//init the user filters array
userFilters = [[NSMutableArray alloc] init];
for(int i=0;i<[searchFilters count];i++)
{
if([self.searchFilters objectAtIndex:i].enabled)
[userFilters addObject:[self.searchFilters objectAtIndex:i]];
}
}