I don't have too much experience with iOS, but I am working on some legacy code. In the project, we use an object as the key of a dictionary:
NSMutableDictionary * dict;
RoleContainer * role = [Class getRole];
[dict setObject:[Class getData] forKey:role];
We have the role passed to another function. When we try to retrieve the data:
data = [dict objectForKey:role];
Sometimes the return value is empty. It happens about 10% of time. I stepped through the code and found out after passing role to the function the reference of the "role" object had been changed! For example, from 0x002bf500 to 0x00222bad.
Why?
In order to play nicely with NSMutableDictionary your RoleContainer class must implement a hash and isEqual methods. Otherwise, equal roles may get recorded twice in the dictionary, or querying by a valid key may fail.
Here is a brief sample of how you could implement your hash/isEqual when your class has an identifying member:
#interface RoleContainer : NSObject
#property (nonatomic, readonly) NSString *name;
- (NSUInteger)hash;
- (BOOL)isEqual:(id)anObject;
#end
#implementation RoleContainer
#synthesize name = _name;
- (NSUInteger)hash {
return [name hash];
}
- (BOOL)isEqual:(id)anObject {
if (![anObject isKindOfClass:[RoleContainer class]]) {
return NO;
}
RoleContainer *other = (RoleContainer*)anObject;
return [_name isEqual:[other name]];
}
#end
In that code, dict is going to be nil, so you're sending messages to nothing. Is it actually pointing to something later on?
I'm assuming your RoleContainer responds to the stringValue method, which might be a good place to look at what is going on if it's overloaded.
If it's using the standard string value, then it's returning the class and memory location. This may not be reliable if someone down the line is resetting objects to keys.
You may also have an issue where the getData object is being released somewhere where it shouldn't be touched. Try enabling NSZombieEnabled in the debugger, or enable ARC.
try
NSMutableDictionary * dict = [NSMutableDictionary dictionary];
Also it would make sense to have a look at your Class. Is it a class, and getRole a class-method? is it an object?
Related
..................
Environment:OSX10.8, Xcode4.5
Reference:
https://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual
/KeyValueCoding/Articles/SearchImplementation.html#//apple_ref/doc/uid
/20000955-CJBBBFFA
If the countOf method and at least one of the other two possible methods are
found, a collection proxy object that responds to all NSArray methods is
returned. Each NSArray message sent to the collection proxy object will result
in some combination of countOf, objectInAtIndex:, and AtIndexes: messages being
sent to the original receiver of valueForKey:.
My steps:
1) Create a property for NSArray* arrs in MyObject.h.
2) in MyObject.m
#implementation MyObject
- (void)setArrs:(NSArray*)arrs
{ _arrs=arrs; }
- (NSUInteger) countOfArrs
{ NSLog("Im here");
return 0;}
- (id) objectInArrsAtIndex:(NSUInteger)index
{ NSLog(#"objectInArrs");
return nil;
}
#end
3) Testing code
MyObject* obj=[[MyObject alloc] init];
NSArray* arr=[NSarray arrayWithObjects:#"abc",nil];
[obj setArrs:arr];
NSLog(#"%d",[[obj valueForKey:#"arrs"]count]);
NSLog(#"%#",[[obj valueForKey:#"arrs"] objectobjectAtIndex:0])
My Question:
I expect it to invoke countOfArrs and objectInArrsAtIndex: automatically,
however, it didn't. All it does is return the normal NSArray, shows count quantity by 1
and 'abc'.
I didn't find any helpful samples,or maybe i misunderstand what the doc says, don't I?
My tangue language is not English, hope i didn't make any ambitious issues.
You have implemented more functionality than what is needed. Your #property declaration is adding the -<key> getter and -set<Key> accessor. As you were able to find out, your getter (called by valueForKey:#"arrs") is returning the actual NSArray assigned to the property which returns its count and object.
The guide you linked to has the reason why this didn't work in step 1 and step 2 of the Default Search Pattern for valueForKey: section.
Basically, your -countOf<Key> and -objectIn<Key>AtIndex: methods are only called when there isn't either a -<key>, -get<Key> or -is<Key> methods.
In order to get this to work, you need to remove the getter by removing the #property declaration, and then you'll need to add an instance variable to save your array.
Make this your interface and it should work
#interface MyObject : NSObject {
NSArray *_arrs;
}
#end
To start let me tell you I am a total Objective-C beginner. This is my problem:
I have a NSMutableArray that stores objects, (Player) that has the name of the player and his/her score.
I am able to add objects to the array using addObject, but I am having trouble traversing this array. This
is how I do it:
// Get the reference to the array
NSMutableArray *myarray = [delegate getArray];
// Create a numerator
NSEnumerator *e = [myarray objectEnumerator];
id object;
while (object = [e nextObject])
{
[object printPlayer];
}
The method printPlayer belongs to the Player class and it just prints the name and the score.
The problem is when I have three players in the array and I am trying to print the content, it reaches this error inside the printPlayer method:
Thread 1: EXC_BAD_ACCESS(code=1, address=0x0000008)
Strangely if I use NSLog(#"%#", object); instead of [object printPlayer]; it prints a reference to the object and does not reach any error.
Anyone could point me what could be the problem when I try to use [object printPlayer]
Cheers
Update 1:
This is my printPlayer method:
-(void) printPlayer
{
NSLog(#"\n\nName: %#\nScore: %d", playerName, playerScore);
}
Update 2:
Player.h:
#interface PROGPlayer : NSObject
#property (nonatomic, assign) NSString *playerName;
#property (nonatomic, assign) int playerScore;
-(id) init: (NSString *) n;
-(void) printPlayer;
#end
Player.m:
#import "PROGPlayer.h"
#implementation PROGPlayer
#synthesize playerName;
#synthesize playerScore;
/**
* Player's class constructor
* #param n Player's name
* #param s Player's score
*/
-init: (NSString *) n
{
if (!(self = [super init])) return nil;
else
{
playerName = n;
playerScore = 0;
}
return self;
}
-(void) printPlayer
{
NSLog(#"\n\nName: %#\nScore: %d", playerName, playerScore);
}
#end
It seems like your problem is in the way you're defining your properties.
You're using assign rather than strong, or copy.
In a nutshell, it's because strong implies that you want your object to be retained.
Using copy implies that you want to create a new copy of an object or a value and set that as value of your property... As Mario and Jarsen explain, using copy is better practice when working with arrays to prevent the array being mutated (i.e. values changed) while it is being enumerated / traversed. Using copy also retains the new object.
If you're using ARC and your objects are not retained, then they will be released automatically by the compiler.
Using assign means that you assume the new object has been retained elsewhere and that you don't want to retain it again.
I suppose what was happening is that you were assigning your variable to your property, but the variable was being released (and hence resulting in nil) and causing the crash.
Here are a few links:
New to Objective C: Need help understanding strong reference vs assign
Objective-C ARC: strong vs retain and weak vs assign
Clarification on assign, retain, copy, strong?
Your playerName property should best be copied instead of assigned
#property (nonatomic, copy) NSString *playerName;
When trying to access the assigned value, the object most likely is gone causing the bad access.
Also remember to release playerName in dealloc when you set the property to copy.
Cheers
You just want to enumerate the array?
for (CustomClass *object in myArray){
[object printPlayer];
}
Either what Mike Z said or the "crude":
for (int i = 0; i < myArray.count; i++) {
CustomClass* object = [myArray objectAtIndex:i];
[object printPlayer];
}
While there are more elegant schemes, you can clearly understand what this one is doing, and how an NS(Mutable)Array is just a simple analog to a standard C array.
I am currently facing the problem to check whether a property of an Object (NSManagedObject) exists or not.
Unfortunately the method
[[MyObject class] respondsToSelector:#selector(myProperty)];
always returns NO.
I think it's because the property generated by CoreData is a new style property ala
#property (nonatomic, strong) NSString *myProperty
So any ideas how to solve this issue?
I would really appreciate all of your suggestions ;)
Thanks in advance!
Alex
[[MyObject class] respondsToSelector:...] asks whether the metaobject responds to that selector. So, in effect, it asks whether there is a class method with that selector. Your code would return YES if you had:
+ (NSString *)myProperty;
It returns NO because you have the equivalent of the instance method:
- (NSString *)myProperty;
You need to call respondsToSelector: on an instance of your class.
You could normally use instancesRespondToSelector: directly on the metaclass (so, [MyObject instancesRespondToSelector:...]) but Core Data synthesises the relevant method implementations only when you create an object, so that's a non-starter. You could however create an instance via the normal NSEntityDescription route and test respondsToSelector: on that.
Since it's all Core Data, an alternative would be to ask the NSManagedObjectModel for the relevant NSEntityDescription via its entitiesByName dictionary and inspect the entity description's propertiesByName dictionary.
The only cases I've required this has been to set things dynamically so I am only looking for the setter. I am just composing the signature for the setter and then testing that it exists and then using it.
NSArray * keys = [myObject allKeys];
for(NSString * key in keys)
{
NSString * string = [NSString stringWithFormat:#"set%#:", [key capitalizedString]];
SEL selector = NSSelectorFromString(string);
if([myObject respondsToSelector:selector] == YES)
{
id object = [dict objectForKey:key];
// To massage the compiler's warnings avoid performSelector
IMP imp = [card methodForSelector:selector];
void (*method)(id, SEL, id) = (void *)imp;
method(myObject, selector, object);
}
}
This code satisfies a need where you may not be digesting all the data you receive in the dictionary.
In this case it was sparse json, so some data may not always exist in the json so stepping thru myObjects attributes looking for their corresponding key would just be a lot of wasted effort.
Are you synthesizing the property in the class file?
#interface SomeClass : NSObject
{
#property (nonatomic, strong) NSString *myProperty
}
#end
#implementation SomeClass
#synthesize myProperty;
#end
I have one question about the variables of an object, I would like to know if I can check if a string is an object's instance variable or not?
Below, an example to illustrate my issue :
I have an object - MyObject.h :
#interface MyObject : NSObject
{
//Variables
id myVariable1;
id myVariable2;
}
#property (nonatomic, retain) id myVariable1;
#property (nonatomic, retain) id myVariable2;
And I have also an array list:
NSArray * myArray = [[NSArray alloc] initWithObjects:#"myVariable1",#"myVariable2",#"myVariable3",#"myVariable4",nil];
I would like to know if it's possible to determinate which strings in the array list aren't defined as variable in the object MyObject.
=> myVariable3 and myVariable4 for this case.
I tried to use "isKindOfClass", "isMemberOfClass", "valueForKeyPath", "valueForKey" but without success... Let me know if you have some advices to resolve my problem :)
Assuming the properties aren't using a custom setter name, you could do:
MyObject *object = ...;
for (NSString *name in myArray) {
SEL getterName = NSSelectorFromString(name);
if ([object respondsToSelector:getterName]) {
NSLog(#"MyObject has a method named %#", getterName);
} else {
NSLog(#"MyObject does not have a method named %#", getterName);
}
}
I would create an object to use for comparison using the NSClassFromString class.
if ([myClass isKindOfClass:NSClassFromString(myClassString)] {
// class matches string
} else {
// class doesn't match
}
It's easier to check if values in the array are properties of some object. As in your case the valueForKey: should have worked only if myInstance1 and myInstance2 are non-nil objects. You'd just need to implement - (id)valueForUndefinedKey: method to return nil and all would be fine and dandy.
You can also try using object_getInstanceVariable method while iterating through an array and fetching each possible instance variable separately. I believe that undeclared instances should return NULL pointer (as opposed to nil instances that are declared but undefined).
Apple's documentation on -setPrimitiveValue:forKey: is vague in two ways when using it to manage to-many relationships.
First they state:
If you try to set a to-many relationship to a new NSMutableSet object, it will (eventually) fail.
Eventually?! What does that even mean? Will it fail later during -[NSManagedObjectContext save:]? When an managed object is turned into a fault and then paged back in? When? Can I write a test case to consistently recreate the failure on-demand?
Second, providing sample code to correctly handle this case, they write:
first get the existing set using primitiveValueForKey: (ensure the method does not return nil)
What should I do if/when the method does return nil? assert() it and fail immediately because that means the entire object graph is corrupted and saving will lead to data loss? NSAssert() on it as a warning to the caller but press on (silently doing nothing)?
Right now I'm simply directly assigning my desired NS[Mutable]Set in that case, like so:
- (void)setChildren:(NSSet*)value_ {
NSMutableSet *mutableRelationshipSet = [[[self primitiveValueForKey:#"children"] mutableCopy] autorelease];
if (mutableRelationshipSet) {
[mutableRelationshipSet setSet:value_];
[self setPrimitiveValue:mutableRelationshipSet forKey:#"children"];
} else {
[self setPrimitiveValue:value_ forKey:#"children"];
}
}
Is that wrong?
Just mutate the return value of -primitiveValueForKey: as a mutable set, trusting that its return value will do the right thing.
Be sure to also use -willChangeValueForKey:withSetMutation:usingObjects: and -didChangeValueForKey:withSetMutation:usingObjects: around your manipulation; you didn't show this in your above code.
- (void)setChildren:(NSSet *)value {
[self willChangeValueForKey:#"children" withSetMutation:NSKeyValueSetSetMutation usingObjects:value];
NSMutableSet *primitiveValue = [self primitiveValueForKey:#"children"];
[primitiveValue setSet:value];
[self didChangeValueForKey:#"children" withSetMutation:NSKeyValueSetSetMutation usingObjects:value];
}
If you can target Leopard, you don't have to worry about this; you can use Core Data's built-in property support instead:
#import <CoreData/CoreData.h>
#interface Folder : NSManagedObject
#property (readwrite, retain) NSSet *children;
#end
#implementation Folder
#dynamic children;
#end
Core Data will generate not only a getter/setter pair for the children property but the mutable-set accessors as well, so you can use -mutableSetValueForKey: on it and manipulate the result with a minimum of overhead.