NSDictionary in iOS 6 - iphone

I have this following code :
NSAssert(dst, #"sdiasiosadiodasio");
// Add to a list so that we don't lose the references
[layersToDsts setObject:dst forKey:layer];
NSLog(#"The key is %lu", layer);
for (Bit *layer in [layersToDsts allKeys])
{
BitHolder *dst = [layersToDsts objectForKey:layer];
NSLog(#"My added key is %lu", layer);
NSAssert(dst, #"ddddd");
}
layersToDsts is a mutable NSDictionary. I'm trying to use the address of an object as the key, mapping to another object.
The code worked on iOS 5. For some reason, the code gives
The key is 484196128
My added key is 484253328
Assertion failure in -[Animator animateImpl:] 'NSInternalInconsistencyException', reason: 'ddddd'
Obviously, the object I added to the dictionary can't be retrieved. The object isn't nil. Why the mapping key is another address?? The code worked perfectly on iOS 5....

CALayer does not implement the NSCopying protocol and so can not be used as a key, use the address of the CALayer and turn it into an NSNumber you ay alternatively be able to use the name property on CALayer.

If you want to use an object address as the key, you should create an object that contains that address. For example
[layersToDsts setObject:dst forKey:[NSValue valueWithPointer:layer]];
However I STRONGLY discourage you from doing it. You should instead come out with a proper key and not depend on any specific object address.

Related

How to avoid the duplicate dictionary objects in NSMutableArray

Hi in one of my application,I have an array which contains a group of NSMutableDictionary objects. The dictionary object have three key-value pairs as like below
company
product
quantity
And array having many number of objects. Here now by using different add buttons I am adding these dictionary objects to the array. Even while adding objects to array i am checking whether any duplicate objects are available or not using NSNotFound method. As such below
if([Array indexOfObject:dicObject] == NSNotFound)
{
[Array addObject:dicObject];
}
Here it is working fine in few cases, But it's not working in other cases.I will explain with one example :
For example i have one dicobject in array with following key value pairs
company:XYZ Product:ABC Quantity:2
Now for example I want to add one more dic object with the same above key value pairs. That time obviously it won't add because already same product is available in array.
This is valid condition.
Exceptional Case: For example I want to add one more product with following values
Company:XYZ Product:ABC Quantity:6
At this case this product is adding into the array without any error. But my concern is i don't want to add this into the array again only the quantity have to update, because company and product name both are same so. So can you please show me the way to handle this scenario.
You could use indexOfObjectPassingTest: to know if a similar dictionary is already present in the array.
This may look something like this:
NSMutableArray *arr = // your array
NSDictionary *dicObject = // your object
NSUInteger indexOfDicObject = [arr indexOfObjectPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop)
{
return ([obj[#"company"] isEqualToString:dicObject[#"company"]] &&
[obj[#"product"] isEqualToString:dicObject[#"product"]]);
}];
if (indexOfDicObject == NSNotFound)
{
[arr addObject:dicObject];
}
else
{
NSNumber *quantity = arr[indexOfDicObject][#"quantity"];
arr[indexOfDicObject][#"quantity"] = #([quantity intValue] + [dicObject[#"quantity"] intValue]);
}
I made the following assumptions:
the company value is a NSString;
the product value is a NSString;
the quantity value is an integer, stored in a NSNumber.
See also trojanfoe's answer, which is better if you can replace your dictionaries by classes.
I think you need to change tack; first create a custom object to hold your company, product and quantity and ensure you implement the isEqual: and hash methods.
Then simply store your custom objects within an NSMutableSet object, which will ensure that duplicates cannot exist.
Your custom object will now become your principle Model object for the app (i.e. provide the 'M' in MVC, the design pattern upon which Cocoa and Cocoa Touch apps are based) and you will find that it will be reused over and over as the app grows.

No warning for id?

I have just spotted something that I am a little puzzled about, I wonder if someone would be so kind as to clarify it for me.
NSArray *nextArray = [NSArray arrayWithObjects:#"ONE", #"TWO", #"THREE", nil];
for(id eachObject in nextArray) {
NSLog(#"COUNT: %d", [eachObject length]);
}
Why does the above not complain/warn about the fact that I am asking for the length of an id?
In Objective-C id is the general type for any kind of object regardless of class and can be used for instances of a class and for class objects themselves.
The id type is completely nonrestrictive it has no information about an object, except that it is an object. So there's no way for the compiler to know whether or not that object can respond to a method because it doesn't know what kind of object it is.
By using it in your code you're basically saying 'to whatever this is pointing to, perform this operation'.
You use id when you specifically do not want compiler type checking. You can send any message to an id type without a warning, and you can assign an id to any other type without a type cast.
This allows you to fetch an object from an array without using a cast. E.g., you're free to assume that the array contains NSStrings:
NSString* someString = [myArray objectAtIndex:1];
It also allows you to send a message to an object without a cast or a warning. In fact, the message you wish to send may not be part of any formal class or protocol:
id someObject = [myArray objectAtIndex:1];
if ([someObject respondsToSelector:#selector(setName:)])
{
[someObject setName:#"Foo"];
}
The compiler will never type check messages sent to an id. It's partly what enables Objective-C's dynamism.
If eachObject was any other type, then you would get an error if the compiler couldn't resolve the method name.
The NSArray might contain different object types, for example:
NSArray *thArray = [[NSArray alloc] initWithObjects:#"Stack",#"Overflow",[NSNumber numberWithInt:10],nil];
for(id theObject in thArray) {
NSLog(#"COUNT: %lu", [theObject length]);
}
The id can represent any object (in this case NSString or NSNumber),
therefor the compiler cannot know whether the primitive method length exists.
Simply, id means all Objective-C Class. so, length method it belongs a NSString Class. Compiler no throw warning to you.
id is determined dynamically at runtime, is unknown at compile time.
Each object in Objective-C knows what class it has and if it can handle a message. It's not the compiler who checks the class, it is the object itself at runtime.
The class of an object can be undefined at compilertime, but at runtime each object has a defined class.

dynamically set the key for Key-Value Coding in objective-c iphone

I have a class called mycallback:
[mycallback setValue:[code objectForKey:#"abc"] forKey:#"abc"];
It gets called for a lot of my controllers. THe thing is i pull data from a mysql database and I send it to mycallback. So I may not know what the key is. And in addition to that, I don't want to declare instance variables inside mycallback class because I want it separated.
Unfortunately, if the code as is runs above, I get this message:
exception 'NSUnknownKeyException', reason: '[<CallbackClass 0x7b6c5d0> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key fence.'
So I want to dynamically set the keys but not inside mycallback. So I create a category for NSDictionary and add valueForUndefinedKey method:
- (id)valueForUndefinedKey:(NSString *)key
{
NSLog(#"This key not being called %#", key);
return nil;
}
but 1) valueForUndefinedKey is not called and 2) I'm not sure what to put in there to make that error go away.
thanks for response.
You've defined valueForUndefinedKey: which is what deals with returning values. But in your code snippet it looks like you are trying to set a value for an undefined key. In which case, the method to use is:
setValue:forUndefinedKey:
An example, based on what it looks like you are trying to do:
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
NSLog(#"Attempting to set value: %# for key: %#", value, key);
}

Generic return type for primitives

Is there a return type for "any primitive" similar to the way you can use NSObject as the return type for any object? I tried using id, but the compiler was giving me an error that it was trying to convert a primitive to a reference.
Here's what I'm trying to do. :
-(void)setObject:(NSObject*)obj forKey:(NSString*)key {
[sharedInstance.variables setObject:obj forKey:key];
}
-(NSObject*)getObjectForKey:(NSString*)key {
return [sharedInstance.variables objectForKey:key];
}
-(void)setValue:(???)value forKey:(NSString*)key {
[sharedInstance.variables setValue:value forKey:key];
}
-(???)getValueForKey:(NSString*)key {
return [sharedInstance.variables valueForKey:key];
}
The alternative that I have though of is to use separate methods (getIntForKey, getFloatForKey, etc.) to access the values.
1) Read Key-Value Coding Article in XCode documentation - all answers are there
2) There's an object NSValue, which resembles your "NSObject". NSValue can store plain-old-data inside itself.
PS
"Scalar and Structure Support
Key-value coding provides support for scalar values and data structures by automatically wrapping, and unwrapping, of NSNumber and NSValue instance values.
Representing Data as Objects
The default implementations of valueForKey: and setValue:forKey: provide support for automatic object wrapping of the non-object data types, both scalars and structs.
Once valueForKey: has determined the specific accessor method or instance variable that is used to supply the value for the specified key, it examines the return type or the data type. If the value to be returned is not an object, an NSNumber or NSValue object is created for that value and returned in its place.
Similarly, setValue:forKey: determines the data type required by the appropriate accessor or instance variable for the specified key. If the data type is not an object, then the value is extracted from the passed object using the appropriate -Value method."
I would have thought id is the perfect candidate here too... could this just be a casting issue you're seeing?
i.e. the id implicitly implies a pointer so in my mind I see id as an objective c equivalent to the c void*
In other words where you have a NSObject* you could replace this with id such as
-(NSObject*)myMethod1
{
}
so can be done for any returned primitive with
-(id)myMethod1
{
}
i.e. not an id*
Also I expect this was just a copy/paste thing but incase it also causes issues
-(void)setValue:(???)value forKey:(NSString*)key {
[sharedInstance.variables setValue:num forKey:key];
}
should probably be
-(void)setValue:(???)value forKey:(NSString*)key {
[sharedInstance.variables setValue:value forKey:key];
}
I eventually worked through this. The ultimate solution was to have separate accessor/mutator methods per type. So now I have setIntForKey, setBoolForKey, getIntForKey, getBoolForKey, etc. The drawback is quite obvious, in that I can't call one method to set values and another to retrieve them. The advantages are numerous, however. Because the compiler knows what object or primitive type the method is expecting at compile time, I gain compile time checking for all of these methods. Additionally, I don't have to worry with casting the retrieved values to their primitive types (obviously the returned NSObjects are a different story).

objectForKey stringValue crashing my app?

I have a class that I use to setup objects in an array. In this class I have a custom "initWithDictionary", where I parse a JSON dictionary. However, as I am running into NSNull, this crashes my app. To get around this, I set up a class that handles exceptions, so when a string is NSNull, it's replace it with #"". or -1 for integers.
This is my NullExtensions class:
#interface NSNull (valueExtensions)
-(int)intValue;
-(NSString *)stringValue;
#end
#implementation NSNull (valueExtensions)
-(int)intValue {
return -1;
}
-(NSString*)stringValue {
return #"";
}
#end
However, in my initWithDictionary method, the following code crashes my app:
self.bookTitle = [[parsedDictionary objectForKey:#"book_title"] stringValue];
It doesn't work regardless of the object in the parsed dictionary being NSNull or containing a valid string. Only if I do the following (and the string is not null):
self.bookTitle = [parsedDictionary objectForKey:#"book_title"];
Is stringValue incorrect in this case? And if so, how do I use it properly in order to setup proper NSNull replacements?
Thx
You really really don't want to add a category to NSNull that adds such common methods. That will change the behavior of NSNull for all instances in the application, including ones created by the underlying frameworks solely for their private use.
If you need a value class that represents the notion of "value doesn't exist and therefore I'm going to return these default values instead", create a class or instance that represents exactly that.
As for why it crashes, I couldn't tell you without seeing the actual details of the crash.
And, yes, it really is THAT bad to add a category to a class that adds such a common method. All it takes is one bit of code in a plug-in or framework that does:
if ([fooMaybeNull respondsToSelector: #selector(intValue)] bar = [fooMaybeNull intValue];
Not terribly farfetched -- I have had to debug nasty crashers or misbehaviors due to exactly this kind of willy-nilly category addition.
If you are going to add methods to a class via categories, prefix your method names so as to isolate them from existing functionality. It is still fragile, but manageably so.
Instead of creating categories on NSNull, for which you would also have to add a similar category to NSString (that's why it crashes, because real strings do not respond to stringValue) - instead try creating a helper category on NSDictionary like "stringForKey" that uses the code Johan posted and returns an NSString, probably also should enforce all other types get mapped to empty strings as well.
The NSNull extensions you have written look ok to me but using a method like stringValue may be confusing since other classes like NSNumber use this.
Personally though, I think NSNull replacement in this instance is unnecessary. If you just made a quick test you can replace the NSNull where you need to. e.g.
id testObject = [parsedDictionary objectForKey:#"book_title"];
self.bookTitle = testObject==[NSNull null] ? #"" : testObject;
You are asking an NSString for its stringValue. No need to convert a string to a string.
Try this:
if (![[parsedDictionary objectForKey:#"book_title"] isKindOfClass:[NSNull class]]) {
self.bookTitle = [parsedDictionary objectForKey:#"book_title"];
} else {
self.bookTitle = #"";
}
Edit: You should not use the category on NSNull you created. You don't need it, nor should you want it. If the source for the dictionary inserts NSNull instances, go ahead and use my code above. Normally you would expect to simple have no value inserted for the key, at which time you can simple see if [parsedDictionary objectForKey:#"book_title"] returns anything.
Are you sure that the dictionary is returning [NSNull null]? By default, dictionaries return nil, not [NSNull null], when an value isn't found for a key.
However, in my initWithDictionary method, the following code crashes my app:
self.bookTitle = [[parsedDictionary objectForKey:#"book_title"] stringValue];
It doesn't work regardless of the object in the parsed dictionary being NSNull or containing a valid string.
That makes sense, since stringValue is not a valid method on NSString. It will work for NSValue and its subclasses, but not NSString.