Objective-C and ARC: Why value stored to during its initialization is never read and Incompatible pointer - objective-c

when I run the analyzer tool I'm getting value stored to (jsonArr and myrrh) during its initialization is never read on lines and Incompatible pointer types assigning to 'NSMutableArray * from 'NsDictionarty *'

Look at the 1st two lines:
NSDictionary *jsonArr = [[NSDictionary alloc] init];
jsonArr = [json objectForKey:#"categories"];
The first line creates and assigns a new dictionary.
The second line then reassigns a new value to the same variable. This throws away the original value. Hence the warning that the value is never used.
Those two lines should simply be:
NSDictionary *jsonArr = [json objectForKey:#"categories"];
Actually, even better would be:
NSDictionary *jsonDict = json[#"categories"];
Don't name a dictionary variable with Arr. It's confusing. And use modern syntax.
The issues with myArr are the same. Though you have the additional problem of trying to assign an NSDictionary to an NSMutableArray. Those two are in no way compatible.
Perhaps jsonArr is properly named and its type should be NSArray instead of NSDictionary. Even then, you can't assign an NSArray to an NSMutableArray. You need to make a mutable copy:
NSArray *jsonArr = json[#"categories"];
NSMutableArray *myArr = [jsonArr mutableCopy];

In Obj-C variables are typed, meaning only values conforming to that variable's type may be assigned.
In your code (which is so hard to read) myArr is of type NSMutableArray * (a reference to an NSMutableArray) while jsonArr is of type NSDictionary* (a reference to an NSDictionary)
You cannot assign the value in jsonArr to myArr without a type conversion operation (cast).

Related

What is the correct way to change a string value in Objective C?

Changing a mutable strings value in many languages is very easy:
myString = "foo"
myString = "bar"
However, while learning Objective C it seems like you have to really, really jump through hoops to change an existing string's value (This is often done with switch or if cases):
NSMutableString *myString;
myString = [NSMutableString stringWithString:#"foo"];
myString = [NSMutableString stringWithString:#"bar"];
Is there an better way to change a string's value?
myString = #"foo"
myString = #"bar"
is fine in obj-c too. Don't confuse changing object value (for which you need NSMutableString) and variable value.
EDIT. Well, maybe you really need to change existing object value, but it's unclear from your question. You don't usually need to mutate existing object for switches.
Objective-C doesn't force you to jump through any extra hoops relative to most languages. It's probably simply less clear what you're doing in other languages perhaps, because for the most part, it's the same.
For starters, Objective-C doesn't allow objects on the stack--only on the heap. This is true for all objects and there's nothing special about strings here. Other languages such as C++ allow objects on the stack.
When we create objects on the heap, our variable is merely a pointer to that object, so when we create an object we have to allocate memory for it.
When we assign a string object via the = operator, we've change the memory location our variable points too.
You can create strings as simply as this:
NSString *fooString = #"foo";
NSString *barString = #"bar";
Which really isn't any more complex then any language I've seen. It's just an extra #, how is this complicated?
CHANGING the value of a string, however, isn't so simple. NSString is immutable, so to change it's value, we have to create a new object on the heap, and point to that. We haven't changed the value, we've created a new object and pointed at that.
With NSMutableString however, there are a range of available methods for changing the actual value at the memory location we point to.
In your example
NSMutableString *myString;
myString = [NSMutableString stringWithString:#"foo"];
myString = [NSMutableString stringWithString:#"bar"];
the first two lines can be grouped together and the third can simply reduce to changing the string property of the mutable string, like this
NSMutableString *myString = [NSMutableString stringWithString:#"foo"];
myString.string = #"bar";
Anyway, mutable strings actually make sense only when you need to pass a reference away and allow it to be changed.
If you simply need to change the value of a NSString * pointer over time, just do
NSString *myString = #"foo";
myString = #"bar";
myString = #"baz";
and be done with it.
It depends on whether you insist on having a string where you can change the bytes inside it or only want to replace it entirely. Replacement of the whole thing doesn't need 'mutable' but if you'd like to replace it and be able to modify the bytes, then you want something like:
NSMutableArray *mstr = [#"foo" mutableCopy];
mstr = [#"bar" mutableCopy];

Why set types in Obj-c fast enumeration loops?

NSMutableArray *array = [[NSMutableArray alloc] init];
NSString *string = #"string";
[array addObject:string];
NSDate *date = [[NSDate alloc] init];
[array addObject:date];
for (*placeholder* stuff in array)
NSLog(#"one");
If I change placeholder to either NSString* or NSDate*, I expect to see "one", because the for loop should just ignore a non-matching type. However, the result is "one one".
Doesn't this imply that you should just have placeholder be id whatever the situation, since it doesn't seem to matter anyhow?
fast enumeration always iterates over all object in a collection. it does not filter.
The only thing that happens is, that you will have some strange casts.
if your array contains objects of differnt classes, you can determine the class for each object with isMemberOfClass:
if you would do for (NSDate *obj in array), any object in the array will be casts to NSDate, no matter if that is sense-full or not. and due to the nature of objective-c it will even work, as-long as you dont send a message that is only understandable by NSDate objects or send the object as an argument to a method that needs to receive a date object, as a cast does not change the object in anyway. A cast is just a promise you make to the compiler that you know what you are doing. Actually you also can call it a lie.
To answer your question title itself: You dont have to set the class inside the loop statement. the generic object type id is sufficient. But usually you have objects of one kind in an array — views, numbers, string, dates,…. by declaring the right class you gain some comfort like better autocompletion.
Yes, using id (or some other common ancestor class) is the correct approach, and then it's necessary to determine which type of class has been enumerated in order to handle it differently:
for (id obj in array)
{
if ([obj isMemberOfClass:[NSString class]])
{
NSString *str = (NSString *)obj;
NSLog("obj is a string: %#", str);
}
else if ([obj isMemberOfClass:[NSDate class]])
{
NSDate *date = (NSDate *)obj;
NSLog("obj is a date: %#", date);
}
}
The problem has nothing to do with fast enumeration, but with collections which can contain any type of object. The same question arises when you access an individual element of an array:
id lastObject = [array lastObject];
or
NSString *string = [array lastObject];
Which will you chose? It all depends on your code. If you're sure that array only contains strings, then in my opinion it is better to use the second choice, because you get additional type checking, autocompletion, and method matching from the compiler (i.e. you won't get warnings if you call a method that has different signatures for two different objects). The same applies to fast enumeration: if your collection can contain any kind of object, use id. If you know what it contains, use the specific type. (And the same also applies to block tests. In NSArray's method
- (NSUInteger)indexOfObjectPassingTest:(BOOL (^)(id obj, NSUInteger idx, BOOL *stop))predicate
if you know it only contains strings for instance, you can replace id with NSString * in the block arguments. It won't change at all the compiled code or the behavior of your application, it will only change the compiler type checking.

'-[__NSCFDictionary setObject:forKey:]: mutating method sent to immutable object'

- (NSMutableDictionary *)updateTemplates:(NSMutableDictionary *)oldTemplates
forSpecType:(NSString *)specType {
// oldTemplates is an NSMutableDictionary pulled from a plist
// specType is used for flexible paths, to eliminate duplicate code
// Make a dict of the parameters object (about to be overwritten)
NSMutableDictionary *parameters = [oldTemplates valueForKeyPath:
[NSString stringWithFormat:#"root.%#.parameters", specType]];
// Dump the new data into the matching object
[oldTemplates setValue:[updateTemplates valueForKeyPath:
[NSString stringWithFormat:#"data.%#", specType]]
forKeyPath:[NSString stringWithFormat:#"root.%#", specType]];
// Put the parameters back, since they don't exist anymore
/* Instant crash, with the debugger claiming something is immutable
* But I just used the exact same method on the line above
* updateTemplates isn't immutable either; it's only when I try to mutate
oldTemplates after putting in updateTemplates -- and only the update
seems to be breaking things -- that I get the exception and crash
*/
[oldTemplates setValue:parameters forKeyPath:
[NSString stringWithFormat:#"root.%#.parameters", specType]];
return oldTemplates;
}
I could set up a loop to write one object of updateTemplates.specType at a time so only those parts get replaced and then I don't have to do anything with the parameters, but if it's immutable now, it will be when I try to write to it again. That won't do me any good.
If I remember correctly, dictionaries created from plists or NSUserDefaults are immutable by default. You'll have to create a mutable copy manually:
NSMutableDictionary *parameters = [[oldTemplates valueForKeyPath:
[NSString stringWithFormat:#"root.%#.parameters", specType]] mutableCopy];
mutableCopy makes a shallow mutable copy, not a deep mutable copy. If you have an NSDictionary containing key/value pairs where the values are NSDictionary instances, mutableCopy will return a mutable dictionary containing those NSDictionary immutable instances as values.
You either need to do a deep copy or use the plist serialization functionality to decode the plist with the mutable collections option enabled. Or you could compose a new collection derived from the old.
You can simply do:
NSMutableDictionary* oldTemplates = [NSMutableDictionary dictionaryWithDictionary:[oldTemplates valueForKeyPath:
[NSString stringWithFormat:#"root.%#.parameters", specType]]];
This will create a mutable copy from an existing NSDictionary

Change pointer to NSString in Objective-C

I have been experimenting a bit with Objective-C and noted some, at least to me, rather strange behavior. First I define a pointer to an NSString and add it to an NSArray:
NSString *s = #"A";
NSArray *a = [NSArray arrayWithOject: s];
I then print out the value of s as well as the contents of a:
NSLog(#"%#", s);
NSLog(#"%#", myArray);
and in both cases the output is A. Now, if I change the pointer s, say
s = #"B";
then the two NSLog statements print out B and A, respectively.
That is, the pointer in my array still points to #"A". After spending years coding in Java, this is very surprising to me. Am I missing something really fundamental here?
Thanks,
Michael Knudsen
The easiest way to understand this is that #"A" creates a new NSString object.
In your code, you set the pointer s to point to this object, and then add the original object (not the pointer) to an array. You then change the address that the pointer points to to a new address.
If you want to change the original object, then use NSMutableString and modify the actual object (instead of changing the pointer to a new object) and they will both update as you expect.
Try:
NSMutableString *s = #"A";
// Add to array
[s setString:#"B"]
That is because NSStrings are immutable, as in Java, so you are not changing the contents of the object stored at address x (the one nsarrsy has), you are pointing s to address y.
The Java and Objective-C behaviors are the same. With the following Java code:
String foo = "foo";
Vector myVector = new Vector();
myVector.add(foo);
System.out.println(foo);
System.out.println(myVector);
foo = "bar";
System.out.println(foo);
System.out.println(myVector);
The following values are printed out:
foo
[foo]
bar
[foo]
Note: The same is true if I use String[] instead of Vector, but an NSArray is more like Vector than String[].
The easiest way to see this is to see that your s = #"B"; simply point s to another string object rather than modifying the original object.
So the array still contain #"A". When you add objects to NSArray you simply make a pointer in NSArray to point to #"A". That never changes.

How to use #encode() to get #"NSArray" in Objective-C

I'm using the runtime functions to get the type of a property (thanks to eJames for helping me to figure out this way).
The attribute string of the property looks like this:
T#"NSArray",&,Vstuff
I need to check if the property type is an array, at the moment I'm doing it like this:
- (BOOL)valueForKeyIsArray:(NSString *)key fromTagret:(id)target
{
NSString *lowerCaseKey = [self convertToKVCKey:key];
objc_property_t property = class_getProperty([target class], [lowerCaseKey UTF8String]);
NSString *propertyAttrs = [NSString stringWithUTF8String:property_getAttributes(property)];
NSString *encodedType = #"#\"NSArray\"";
NSRange range = [propertyAttrs rangeOfString:encodedType options:NSLiteralSearch];
return range.location != NSNotFound;
}
But since Apple can change the type definition string at any time, I would like to generate this #"NSArray" type string. I tried it with #encode(), but it did not work:
NSString *encodedType = [NSString stringWithUTF8String:#encode(NSArray *)];
So how can I generate this type string? Or is there a better way to check if this property attributes contain the array type?
There is no way to check this. In Objective-C source code the variables being typed as NSArray * is only there for the compiler to issue warnings. It has no meaning, and does not exist at runtime. If you mis-typed an NSArray as an NSString, you would get lots of warnings when compiling, but your code would behave exactly the same when run. At runtime all that is known is that the ivar/property is "an object".
Another way to think of it, is that once Objective-C is compiled, all object references are id references.
Just accept that if the runtime changes, your code will break, and move on. However, I think you might be miscategorizing ivars of type NSMutableArray *, CFArrayRef, or CFMutableArrayRef. You also seem to be assuming all keys correspond directly to a declared property.
The cleanest solution might be to assert that the sample object being used for the test (the target) must have a non-nil value for that key, and just grab the value and test that [[target valueForKey:key] isKindOfClass:[NSArray class]].