So I was playing with something where the class type of the arg is unknown until runtime.
like this:
- (NSNumber *)doWhatever:(id)arg
{
// this ALWAYS FAILS
if ([arg isKindOfClass:[NSNumber class]]) {
return arg;
}
else {
// what was it???
NSLog("arg klass=%#", [arg class]); // prints NSCFNumber
}
// This check works correctly.
if ([arg isKindOfClass:[NSArray class]]) {
for (id x in arg) {
NSNumber *result = [self doWhatever:x];
if (result) {
return result;
}
}
}
return nil;
}
- (void)someMethod
{
NSArray *myArray = [NSArray arrayFromObjects:[NSNumber numberWithInt:3], nil]];
NSNumber *myNum = [self doWhatever:myArray];
NSLog(#"myNum=%#", myNum);
}
This is obviously a contrived example of what I'm trying to do.
The point is this never works b/c the class of "arg" always appears as NSCFNumber, and I can't figure out a way to check for that.
Any way to make it less confusing to detect whether an arbitrary value in an array is an integer or not?
UPDATE:
Thanks #chuck, #omz, and #Nikita Leonov for your help. What I posted here originally was just a simplification of the problem I was having and wrote it here without running it first. That code once updated to remove the errors (see below) runs fine actually.
The mistake I made in my real code that I was having trouble with was equally silly--I was passing the array back in to "doWhatever" instead of the item at array's index, which is why I was having problems.
Thanks for trying to help, however misguided my question was.
Sorry for wasting everybody's time!
Corrected code that runs as desired:
- (NSNumber *)doWhatever:(id)arg
{
// this NOW WORKS
if ([arg isKindOfClass:[NSNumber class]]) {
return arg;
}
else {
// what was it???
NSLog(#"arg klass=%#", [arg class]); // prints NSCFNumber
}
// This check works correctly.
if ([arg isKindOfClass:[NSArray class]]) {
for (id x in arg) {
NSNumber *result = [self doWhatever:x];
if (result) {
return result;
}
}
}
return nil;
}
- (void)someMethod
{
NSArray *myArray = [NSArray arrayWithObjects:
[NSNumber numberWithInt:1],
[NSNumber numberWithInt:2],
[NSNumber numberWithInt:3],
[NSNumber numberWithInt:4],
nil];
NSNumber *myNum = [self doWhatever:myArray];
NSLog(#"myNum=%#", myNum);
}
NSCFNumber is a subclass of NSNumber. As long as you're using isKindOfClass: rather than isMemberOfClass: or [arg class] == [NSNumber class], it should work. If not, your problem is elsewhere.
Related
I'm a developer from Python world used to using exceptions. I found in many places that using exceptions is not so wise here, and did my best to convert to NSErrors when needed. but then I encounter this:
NSMutableArray *results;
for (NSDictionary *dict in dicts)
{
// Memory management code omitted
SomeModel *model = [[SomeModel alloc] init];
model.attr1 = [[dict objectForKey:#"key1"] integerValue];
model.attr2 = [[dict objectForKey:#"key2"] integerValue];
model.attr3 = [[dict objectForKey:#"key3"] integerValue];
model.attr4 = [[dict objectForKey:#"key4"] integerValue];
[results addObject:model];
}
with some of the objects in dict containing NSNull, which would result an "unrecognized selector" exception. In that case, I want to drop that datum completely. My first instinct is to wrap the whole content of the for block into a #try-#catch block:
NSMutableArray *results;
for (NSDictionary *dict in dicts)
{
#try
{
SomeModel *model = [[SomeModel alloc] init];
model.attr1 = [[dict objectForKey:#"key1"] integerValue];
model.attr2 = [[dict objectForKey:#"key2"] integerValue];
model.attr3 = [[dict objectForKey:#"key3"] integerValue];
model.attr4 = [[dict objectForKey:#"key4"] integerValue];
[results addObject:model];
}
#catch(NSException *exception)
{
// Do something
}
}
But is this a good approach? I can't come up with a solution without repeating checks on each variable, which is really ugly IMO. Hopefully there are alternatives to this that haven't occur to me. Thanks in advance.
The proper Objective-C way to do this would be:
for (NSDictionary *dict in dicts)
{
if (! [dict isKindOfClass:[NSDictionary class]])
continue;
// ...
}
Testing if a receiver can respond to a message before sending it is a typical pattern in Objective-C.
Also, take note that exceptions in Objective-C are always a programmer error and are not used for normal execution flow.
Many people use a category on NSDictionary for these cases:
- (id)safeObjectForKey:(id)aKey
{
id obj = [self objectForKey:aKey];
if ([obj isKindOfClass:[NSNull class]])
{
return nil;
}
return obj;
}
You still need to make sure, that your dict is an actual dictionary instance.
In the end I decided to solve the problem using KVC. Something like this:
- (id)initWithPropertyDictionary:(NSDictionary *)dict
lookUpTable:(NSDictionary *)keyToProperty
{
self = [self init];
for (NSString *key in dict)
{
NSString *propertyName;
if ([keyToProperty objectForKey:key])
propertyName = [keyToProperty objectForKey:key];
else
propertyName = key;
if ([[dict objectForKey:key] isKindOfClass:[NSNull class]])
{
[self release];
return nil;
}
else
{
[self setValue:[dict objectForKey:key] forKey:propertyName];
}
}
}
The setback of this resolution is that I'll have to use NSNumber for my properties, but for JSON data there is really no distinction between floating numbers and integers, so this is fine.
And if you really want primitive types, you can couple this method with custom setters that converts those NSNumbers into appropriate types.
With this, all you need to do is check for nil before adding the object into the array. Much cleaner everywhere except the model class.
Thanks to jaydee3 for inspiring me to focus on changing the model class.
This question already has an answer here:
Closed 10 years ago.
Possible Duplicate:
'isMemberOfClass' returning 'NO' when custom init
I've some trouble with the "isMemberOfClass"-Method.
I have a class, that generates and returns objects ("MyObject")
// ObjectFactory.h
...
-(MyObject*)generateMyObject;
...
// ObjectFactory.m
...
-(MyObject*)generateMyObject
{
MyObject *obj = [[MyObject alloc]init];
obj.name = #"Whatever"; // set properties of object
return obj;
}
...
And there's a unittest-class, that calls the generateMyObject-selector and checks the class of the returned object:
...
ObjectFactory *factory = [[ObjectFactory alloc]init];
MyObject *obj = [factory generateMyObject];
if (![obj isMemeberOfclass:[MyObject class]])
STFail(#"Upps, object of wrong class returned...");
else
...
I expect, that the else-part is processed...but the STFail(...) is called instead, but why?
Thx for any help!
Regards,
matrau
Ok, here is the original copy&pasted code:
//testcase
- (void)test001_setCostumeFirstCostume
{
NSString *xmlString = #"<Bricks.SetCostumeBrick><costumeData reference=\"../../../../../costumeDataList/Common.CostumeData\"/><sprite reference=\"../../../../..\"/></Bricks.SetCostumeBrick>";
NSError *error;
NSData *xmlData = [xmlString dataUsingEncoding:NSASCIIStringEncoding];
GDataXMLDocument *doc = [[GDataXMLDocument alloc] initWithData:xmlData
options:0 error:&error];
SetCostumeBrick *newBrick = [self.parser loadSetCostumeBrick:doc.rootElement];
if (![newBrick isMemberOfClass:[SetCostumeBrick class]])
STFail(#"Wrong class-member");
}
// "MyObject"
#implementation SetCostumeBrick
#synthesize indexOfCostumeInArray = _indexOfCostumeInArray;
- (void)performOnSprite:(Sprite *)sprite fromScript:(Script*)script
{
NSLog(#"Performing: %#", self.description);
[sprite performSelectorOnMainThread:#selector(changeCostume:) withObject:self.indexOfCostumeInArray waitUntilDone:true];
}
- (NSString*)description
{
return [NSString stringWithFormat:#"SetCostumeBrick (CostumeIndex: %d)", self.indexOfCostumeInArray.intValue];
}
#end
// superclass of SetCostumeBrick
#implementation Brick
- (NSString*)description
{
return #"Brick (NO SPECIFIC DESCRIPTION GIVEN! OVERRIDE THE DESCRIPTION METHOD!";
}
//abstract method (!!!)
- (void)performOnSprite:(Sprite *)sprite fromScript:(Script*)script
{
#throw [NSException exceptionWithName:NSInternalInconsistencyException
reason:[NSString stringWithFormat:#"You must override %# in a subclass", NSStringFromSelector(_cmd)]
userInfo:nil];
}
#end
// the "factory" (a xml-parser)
- (SetCostumeBrick*)loadSetCostumeBrick:(GDataXMLElement*)gDataSetCostumeBrick
{
SetCostumeBrick *ret = [[SetCostumeBrick alloc] init];
NSArray *references = [gDataSetCostumeBrick elementsForName:#"costumeData"];
GDataXMLNode *temp = [(GDataXMLElement*)[references objectAtIndex:0]attributeForName:#"reference"];
NSString *referencePath = temp.stringValue;
if ([referencePath length] > 2)
{
if([referencePath hasSuffix:#"]"]) //index found
{
NSString *indexString = [referencePath substringWithRange:NSMakeRange([referencePath length]-2, 1)];
ret.indexOfCostumeInArray = [NSNumber numberWithInt:indexString.intValue-1];
}
else
{
ret.indexOfCostumeInArray = [NSNumber numberWithInt:0];
}
}
else
{
ret.indexOfCostumeInArray = nil;
#throw [NSException exceptionWithName:NSInternalInconsistencyException
reason:[NSString stringWithFormat:#"Parser error! (#1)"]
userInfo:nil];
}
NSLog(#"Index: %#, Reference: %#", ret.indexOfCostumeInArray, [references objectAtIndex:0]);
return ret;
}
SOLUTION:
Eiko/jrturton gave me a link to the solution - thx: isMemberOfClass returns no when ViewController is instantiated from UIStoryboard
The problem was, that the classes were included in both targets (app and test bundle)
Thank you guys for your help :)
You generally want isKindOfClass:, not isMemberOfClass. The isKindOfClass: will return YES if the receiver is a member of a subclass of the class in question, whereas isMemberOfClass: will return NO in the same case.
if ([obj isKindOfClass:[MyObject class]])
For example,
NSArray *array = [NSArray array];
Here [array isMemberOfClass:[NSArray class]] will return NO but [array isKindOfClass:[NSArray class]] will return YES.
Ok, with different class addresses per your comment, I think I can track this down to be a duplicate of this:
isMemberOfClass returns no when ViewController is instantiated from UIStoryboard
Basically, your class is included twice.
I'm setting values for properties of my NSManagedObject, these values are coming from a NSDictionary properly serialized from a JSON file. My problem is, that, when some value is [NSNull null], I can't assign directly to the property:
fight.winnerID = [dict objectForKey:#"winner"];
this throws a NSInvalidArgumentException
"winnerID"; desired type = NSString; given type = NSNull; value = <null>;
I could easily check the value for [NSNull null] and assign nil instead:
fight.winnerID = [dict objectForKey:#"winner"] == [NSNull null] ? nil : [dict objectForKey:#"winner"];
But I think this is not elegant and gets messy with lots of properties to set.
Also, this gets harder when dealing with NSNumber properties:
fight.round = [NSNumber numberWithUnsignedInteger:[[dict valueForKey:#"round"] unsignedIntegerValue]]
The NSInvalidArgumentException is now:
[NSNull unsignedIntegerValue]: unrecognized selector sent to instance
In this case I have to treat [dict valueForKey:#"round"] before making an NSUInteger value of it. And the one line solution is gone.
I tried making a #try #catch block, but as soon as the first value is caught, it jumps the whole #try block and the next properties are ignored.
Is there a better way to handle [NSNull null] or perhaps make this entirely different but easier?
It might be a little easier if you wrap this in a macro:
#define NULL_TO_NIL(obj) ({ __typeof__ (obj) __obj = (obj); __obj == [NSNull null] ? nil : obj; })
Then you can write things like
fight.winnerID = NULL_TO_NIL([dict objectForKey:#"winner"]);
Alternatively you can pre-process your dictionary and replace all NSNulls with nil before even trying to stuff it into your managed object.
Ok, I've just woke up this morning with a good solution. What about this:
Serialize the JSON using the option to receive Mutable Arrays and Dictionaries:
NSMutableDictionary *rootDict = [NSJSONSerialization JSONObjectWithData:_receivedData options:NSJSONReadingMutableContainers error:&error];
...
Get a set of keys that have [NSNull null] values from the leafDict:
NSSet *nullSet = [leafDict keysOfEntriesWithOptions:NSEnumerationConcurrent passingTest:^BOOL(id key, id obj, BOOL *stop) {
return [obj isEqual:[NSNull null]] ? YES : NO;
}];
Remove the filtered properties from your Mutable leafDict:
[leafDict removeObjectsForKeys:[nullSet allObjects]];
Now when you call fight.winnerID = [dict objectForKey:#"winner"]; winnerID is automatically going to be (null) or nil as opposed to <null> or [NSNull null].
Not relative to this, but I also noticed that it is better to use a NSNumberFormatter when parsing strings to NSNumber, the way I was doing was getting integerValue from a nil string, this gives me an undesired NSNumber of 0, when I actually wanted it to be nil.
Before:
// when [leafDict valueForKey:#"round"] == nil
fight.round = [NSNumber numberWithInteger:[[leafDict valueForKey:#"round"] integerValue]]
// Result: fight.round = 0
After:
__autoreleasing NSNumberFormatter* numberFormatter = [[NSNumberFormatter alloc] init];
fight.round = [numberFormatter numberFromString:[leafDict valueForKey:#"round"]];
// Result: fight.round = nil
I wrote a couple of category methods to strip nulls from a JSON-generated dictionary or array prior to use:
#implementation NSMutableArray (StripNulls)
- (void)stripNullValues
{
for (int i = [self count] - 1; i >= 0; i--)
{
id value = [self objectAtIndex:i];
if (value == [NSNull null])
{
[self removeObjectAtIndex:i];
}
else if ([value isKindOfClass:[NSArray class]] ||
[value isKindOfClass:[NSDictionary class]])
{
if (![value respondsToSelector:#selector(setObject:forKey:)] &&
![value respondsToSelector:#selector(addObject:)])
{
value = [value mutableCopy];
[self replaceObjectAtIndex:i withObject:value];
}
[value stripNullValues];
}
}
}
#end
#implementation NSMutableDictionary (StripNulls)
- (void)stripNullValues
{
for (NSString *key in [self allKeys])
{
id value = [self objectForKey:key];
if (value == [NSNull null])
{
[self removeObjectForKey:key];
}
else if ([value isKindOfClass:[NSArray class]] ||
[value isKindOfClass:[NSDictionary class]])
{
if (![value respondsToSelector:#selector(setObject:forKey:)] &&
![value respondsToSelector:#selector(addObject:)])
{
value = [value mutableCopy];
[self setObject:value forKey:key];
}
[value stripNullValues];
}
}
}
#end
It would be nice if the standard JSON parsing libs had this behaviour by default - it's almost always preferable to omit null objects than to include them as NSNulls.
Another method is
-[NSObject setValuesForKeysWithDictionary:]
In this scenario you could do
[fight setValuesForKeysWithDictionary:dict];
In the header NSKeyValueCoding.h it defines that "Dictionary entries whose values are NSNull result in -setValue:nil forKey:key messages being sent to the receiver.
The only downside is you will have to transform any keys in the dictionary to keys that are in the receiver. i.e.
dict[#"winnerID"] = dict[#"winner"];
[dict removeObjectForKey:#"winner"];
I was stuck with the same problem, found this post, did it in a slightly different way.Using category only though -
Make a new category file for "NSDictionary" and add this one method -
#implementation NSDictionary (SuperExtras)
- (id)objectForKey_NoNSNULL:(id)aKey
{
id result = [self objectForKey:aKey];
if(result==[NSNull null])
{
return nil;
}
return result;
}
#end
Later on to use it in code, for properties that can have NSNULL in them just use it this way -
newUser.email = [loopdict objectForKey_NoNSNULL:#"email"];
Thats it
I'm having a hard time scraping together enough snippets of knowledge to implement an NSOutlineView with a static, never-changing structure defined in an NSArray. This link has been great, but it's not helping me grasp submenus. I'm thinking they're just nested NSArrays, but I have no clear idea.
Let's say we have an NSArray inside an NSArray, defined as
NSArray *subarray = [[NSArray alloc] initWithObjects:#"2.1", #"2.2", #"2.3", #"2.4", #"2.5", nil];
NSArray *ovStructure = [[NSArray alloc] initWithObjects:#"1", subarray, #"3", nil];
The text is defined in outlineView:objectValueForTableColumn:byItem:.
- (id)outlineView:(NSOutlineView *)ov objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)ovItem
{
if ([[[tableColumn headerCell] stringValue] compare:#"Key"] == NSOrderedSame)
{
// Return the key for this item. First, get the parent array or dictionary.
// If the parent is nil, then that must be root, so we'll get the root
// dictionary.
id parentObject = [ov parentForItem:ovItem] ? [ov parentForItem:ovItem] : ovStructure;
if ([parentObject isKindOfClass:[NSArray class]])
{
// Arrays don't have keys (usually), so we have to use a name
// based on the index of the object.
NSLog([NSString stringWithFormat:#"%#", ovItem]);
//return [NSString stringWithFormat:#"Item %d", [parentObject indexOfObject:ovItem]];
return (NSString *) [ovStructure objectAtIndex:[ovStructure indexOfObject:ovItem]];
}
}
else
{
// Return the value for the key. If this is a string, just return that.
if ([ovItem isKindOfClass:[NSString class]])
{
return ovItem;
}
else if ([ovItem isKindOfClass:[NSDictionary class]])
{
return [NSString stringWithFormat:#"%d items", [ovItem count]];
}
else if ([ovItem isKindOfClass:[NSArray class]])
{
return [NSString stringWithFormat:#"%d items", [ovItem count]];
}
}
return nil;
}
The result is '1', '(' (expandable), and '3'. NSLog shows the array starting with '(', hence the second item. Expanding it causes a crash due to going 'beyond bounds.' I tried using parentForItem: but couldn't figure out what to compare the result to.
What am I missing?
The example behind the link you included shows an NSDictionary taking care of the subarray stuff, if I'm reading it correctly. So I think your ovStructure should not be an array but a dictionary. But, more fundamentally, I think you should really look into NSTreeController. Unfortunately, NSTreeController is notoriously hard to work with, but improvements were made last year and even I got it working in the end. Good luck.
here's a very basic question, that I'm sure you will be able to answer quickly. Please don't laugh at my ignorance.
I have a string, that I want to compare to an array of strings. Only if the string is not part of the array, I want to perform an operation. I tried the following code, that doesn't work. I do understand why, but I just can't think of a way to do it correctly.
Please help me out of my misery.
Thanks in advance
Sjakelien
-(void) findRedundant: (NSString *) aString {
#define ALPHA_ARRAY [NSArray arrayWithObjects: #"A", #"B", #"C", nil]
NSUInteger f;
for (f = 0; f < [ALPHA_ARRAY count]; f++)
{
NSString * stringFromArray = [ALPHA_ARRAY objectAtIndex:f];
if ([aString isEqualToString:stringFromArray]) {
// do nothing
} else {
//do something
}
}
}
[self findRedundant:#"D"];
Your code appears to work fine. Its terrible code, but it works fine, the // do nothing section is called for any match and the // do something section is called for each mismatch in the array. I suspect the problem is that you are expecting the // do nothing section to be executed once if there is no match, and // do something section to be executed once if there is any match, which is not the case. You probably want:
-(void) findRedundant: (NSString *) aString {
#define ALPHA_ARRAY [NSArray arrayWithObjects: #"A", #"B", #"C", nil]
BOOL found = NO;
NSUInteger f;
for (f = 0; f < [ALPHA_ARRAY count]; f++) {
NSString * stringFromArray = [ALPHA_ARRAY objectAtIndex:f];
if ([aString isEqualToString:stringFromArray]) {
found = YES;
break;
}
}
if ( found ) {
// do found
} else {
// do not found
}
}
Also, you clearly do not understand macros and when you should and should not use them (generally, you should never use them, with very few exceptions). The macro is textually substitued in to your code. That means the array creation and initialization is happening every time you use ALPHA_ARRAY. This is terrible.
Basically, never use #define again (except for constants) until you have a much deeper grasp of what you're doing. In this case, you would create the array as taebot described:
NSArray* alphaArray = [NSArray arrayWithObjects: #"A", #"B", #"C", nil];
Next, if you are developing for a modern platform (10.5 or iPhone), you can use Fast Enumeration which is much easier and clearer to read:
-(void) findRedundant: (NSString *) aString {
NSArray* alphaArray = [NSArray arrayWithObjects: #"A", #"B", #"C", nil];
BOOL found = NO;
for ( NSString* stringFromArray in alphaArray ) {
if ([aString isEqualToString:stringFromArray]) {
found = YES;
break;
}
}
if ( found ) {
// do found
} else {
// do not found
}
}
And finally, you should go read through the documentation on NSArray and NSString to see what you can do for free, and then you'll find methods like containsObject that KiwiBastard pointed out, and you can rewrite your routine as:
-(void) findRedundant: (NSString *) aString {
NSArray* alphaArray = [NSArray arrayWithObjects: #"A", #"B", #"C", nil];
if ( [alphaArray containsObject: aString] ) {
// do found
} else {
// do not found
}
}
I'm not sure why your code above isn't working, but have you tried:
if ([ALPHA_ARRAY containsObject:aString])
which will return YES if aString exists otherwise NO
The #define looks odd to me. I think that each time you use ALPHA_ARRAY a different NSArray instance will be created. It would be cleaner to use the containsObject: method on NSArray:
NSArray* alphaArray = [NSArray arrayWithObjects: #"A", #"B", #"C", nil];
if (![alphaArray containsObject:aString]) {
// do something
}