NSString unexpectedly becomes __NSCFDictionary - objective-c

I have this very strange problem, I'm new to objective-c and it probably comes from depths which I don't comprehend yet.
So, in my header file I declare the variable
NSString *curTitle;
then in .m file I synthesize it:
curTitle = [[NSString alloc] init];
after that in other method I assign it:
curTitle = string; // string is an instance of NSString
and at the end of the day when I'm trying to assign
slide.title = curTitle; //slide is a managed object (CoreData)
I'm getting this error: *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Unacceptable type of value for attribute: property = "title"; desired type = NSString; given type = __NSCFDictionary; value = {
}.'
Interesting fact that in iphone SDK 3.2 it worked, but after I installed SDK 4 I have this error
Another interesting fact that if make my curTitle property of my class (with #property and #synthesize) it also works
Any ideas?
Thanks

When an object seems to change class, it's almost always because it has been deallocated, and another object is now residing on the same memory location. All object variables are actually pointers to memory locations (that's what the * means), and unlike in many other languages you have to always remember that you're working with memory locations. (Well, not always, once you get the hang of it. But definitely when you're debugging.)
To debug these problems, it can be very useful to use NSZombie. Just search SO or the web for info.
then in .m file I synthesize it:
curTitle = [[NSString alloc] init];
This is not synthesizing. What you are doing here is merely assigning a value to the variable. More precisely, you create an object somewhere in memory (alloc) and initialize it (init) and then you set the value of curTitle to point to that memory location.
There is no need for you to have this line in your code at all.
curTitle = string;
Here you are overwriting the old value of curTitle with a pointer to another string. Your problem is most likely that that other string later gets deallocated, and the memory location gets reused to hold another object (a dictionary in this case). (If you don't know about the retain/release mechanisms, you need to read up on those to understand what happens.)
slide.title = curTitle;
Since curTitle is pointing to a memory location that has been re-used to hold a random object (or even worse: garbage) this line will fail. In fact, you're lucky that it fails, because if it didn't (if the location hadn't been reused yet) it would be even harder to detect the bug.
What you should do is to declare a property, then synthesize it and access the variable through the property:
self.curTitle = aString;
This will make sure that you actually copy and take ownership of the string so that even if the original string is released, you will still have a valid copy.

Related

Archiving an object and then unarchiving it

NSString *cachePath= [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0];
cachePath= [cachePath stringByAppendingPathComponent:#"nerd.archive"];
// Load the cached channel
RSSChannel *cachedChannel= [NSKeyedUnarchiver unarchiveObjectWithFile:cachePath];
NSLog(#"unarchived data- %# %p, x value- %d",cachedChannel,cachedChannel,cachedChannel.x);
// if one hasn't already been cached, create a blank one to fill up
if (!cachedChannel) {
cachedChannel= [[RSSChannel alloc] init];
NSLog(#"cachedChannel initialised- %# %p",cachedChannel,cachedChannel);
cachedChannel.x=5;
}
In the above code-snippet, the pointer variable cachedChannel is assigned with the return value of unarchiveObjectWithFile: message. Now obviously in the first run, this would return nil but the pointer will be initialized later on in the “if-statement”. Lets say the cachedChannel var is something like
cachedChannel= [RSSChannel:0X123ff]
After the code has gone through its first run, the object assigned to cachedChannel would become serialized.
When i run the test project the second time and the unarchiveObjectWithFile: message is passed so that the serialized object is returned and assigned to the cachedChannel pointer var, it shows up as a different object with a different object-id.
Instead of cachedChannel pointing to [RSSChannel:0X123ff] object, it is now holding some other object like [RSSChannel:0X445ee]
How could this be possible?? Shouldn’t the object that was serialized before be the one to be unarchived later on with the same object-id residing in the same heap memory location?
How could this be possible?? Shouldn’t the object that was serialized before be the one to be unarchived later on with the same object-id residing in the same heap memory location?
Not at all. This is, as you say, happening later. And at this later time, the memory situation is completely different. Think of it this way: if you have code that creates an object from scratch, e.g. [[MyObject alloc] init], and you run the app today and then quit it and run the app again tomorrow, those two instances of MyObject, even though they play the very same role in the life of the app, will have two different memory addresses.
Moreover, what we are creating as we unarchive the object is a different instance from the one that was archived - identical, in whatever ways you have specified while archiving / unarchiving, to the original, but a different instance. Think of it this way: archive-unarchive is an elaborate way of making a copy of the object - and two copies of one instance are, obviously, two different objects.
After all, you could archive the object, hang on to the original, and immediately unarchive the archived object. That would be two different objects. But they could not possibly live at the same memory address!
It sounds like you may be trying to use the memory address as some sort of unique identifier. Beware of that. If a thing needs a unique identifier, give it a unique identifier as a property. Don't rely on the memory address at runtime for anything, except during debugging to confirm that two instances are one and the same instance.

Objects with the same name, what does that mean?

I thought I had a decent understanding of objects, but I guess not. What happens when two objects are the same name? They are both pointing to the same location in memory? So if I had one class that said:
SomeClass *someObject = [SomeClass new];
someObject.text = #"test";
And another class instantiates the same object with the same name:
SomeClass *someObject = [SomeClass new];
someObject.textColor = [UIColor redColor];
This would modify the same object to be a red text that says "test" right ?
Thanks!
~Carpetfizz
No, those two pointers point to two different objects in the memory. It doesn't matter if they have the same name. They were allocated and initialized separately in two different classes.
Btw, you should never use the new method to allocate and initalize the object. The new message is discouraged, as allocation and initialization are two different processes. You should use this instead:
SomeClass *someObject = [[SomeClass alloc] init];
Nope. Just because two variables happen to share the same name does not mean they share the same memory location. When this compiles, the compiler strips the variable names (but not the class names) and calculates memory offsets and messages instead of names and classes. Besides, as a local variable, as soon as they pass out of scope -most likely at the end of each function that created them- they will be destroyed immediately.
No. You can't do that.
Any modern-day compiler will attempt to strangle you before compiling that code - for that exact reason: It doesn't know what to do!
Even if you could get the compiler to make it work, just because the two objects have the same name doesn't mean they have the same memory address.

ARC and __unsafe_unretained

I think I have a pretty good understanding of ARC and the proper use cases for selecting an appropriate lifetime qualifiers (__strong, __weak, __unsafe_unretained, and __autoreleasing). However, in my testing, I've found one example that doesn't make sense to me.
As I understand it, both __weak and __unsafe_unretained do not add a retain count. Therefore, if there are no other __strong pointers to the object, it is instantly deallocated (with immutable strings being an exception to this rule). The only difference in this process is that __weak pointers are set to nil, and __unsafe_unretained pointers are left alone.
If I create a __weak pointer to a simple, custom object (composed of one NSString property), I see the expected (null) value when trying to access a property:
Test * __weak myTest = [[Test alloc] init];
myTest.myVal = #"Hi!";
NSLog(#"Value: %#", myTest.myVal); // Prints Value: (null)
Similarly, I would expect the __unsafe_unretained lifetime qualifier to cause a crash, due to the resulting dangling pointer. However, it doesn't. In this next test, I see the actual value:
Test * __unsafe_unretained myTest = [[Test alloc] init];
myTest.myVal = #"Hi!";
NSLog(#"Value: %#", myTest.myVal); // Prints Value: Hi!
Why doesn't the __unsafe_unretained object become deallocated?
[EDIT]: The object is being deallocated... if I try to substitute lines 2 - 3 with NSLog(#"%#", myTest); the app crashes (and an overridden dealloc in Test is being called immediately after the first line). I know that immutable strings will continue to be available even with __unsafe_unretained, and that a direct pointer to the NSString would work. I am just surprised that I could set a property on a deallocated object (line 2), and that it could later be dereferenced from a pointer to the deallocated object it belonged to (line 3)! If anyone could explain that, it would definitely answer my question.
I am just surprised that I could set a property on a deallocated object (line 2), and that it could later be dereferenced from a pointer to the deallocated object it belonged to (line 3)! If anyone could explain that, it would definitely answer my question.
When the object is deallocated it is not zeroed. As you have a pointer to the deallocated object and the property value is stored at some offset to that pointer it is possible that storing and retrieving that property value will succeed after deallocation, it is also quite possible that everything will blow up for some reason or other.
That your code works is quite fragile, try debugging it with "Show Disassembly While Debugging" and stepping through, you'll probably hit an access violation, or take down Xcode itself...
You should never be surprised that strange things happen in C, Objective-C, C++ or any of the family; instead reserve your surprise for so few strange things happening!
Because the constant string in objc is a constant pointer to a heap address and the address is still valid.
edited after comment:
Maybe because the memory at the test objects address hasn't been overwritten and still contains that object? Speculating....
You can see when Test is deallocated by implementing its -dealloc method and adding some simple logging.
However, even if Test is deallocated immediately, the memory it occupied in RAM may remain unchanged at the time you call myVal.
#"hi!" produces a static global constant string instance that is, effectively, a singleton. Thus, it'll never be deallocated because it wasn't really allocated in the first place (at least, it really isn't a normal heap allocation).
Anytime you want to explore object lifespan issues, always use a subclass of NSObject both to guarantee behavior and to make it easy to drop in logging hooks by overriding behavior.
Nothing strange there…
You need to have at least 1 strong reference to object to keep it alive.
Test * anTest = [[Test alloc] init];
Test * __weak myTest = anTest;
myTest.myVal = #"Hi!";
NSLog(#"Value: %#", myTest.myVal); // Prints Value: (Hi)

Re-initialize NSMutableArray as NSMutableArray

I was having a problem with my app throwing an exception when calling removeObjectAtIndex on an NSMutableArray, saying that myLocationsArray was declared immutable. All other manipulation on that array was fine, it was most definitely declared correctly etc etc but somewhere in my app it was getting set as immutable. After investigating for a while trying to find where it was getting set immutable, I decided screw it and just redeclared the variable as such:
myLocationsArray = [[NSMutableArray alloc] initWithArray:[defaults
objectForKey:MYLOCATIONSARRAY_KEY]];
right before the removeObjectAtIndex call.
However I know this has got to be badwrong, I'm calling alloc/init twice on the same variable. However it's the only thing that has worked. Is there any way to remind this variable that it is an NSMutableArray without introducing memory leaks like I am?
NSUserDefaults returns immutable copy of your array. It doesn't matter whether you put NSArray or NSMutableArray in it, it always give you immutable copy back.
So, do this to get a mutable copy that you can work with
myLocationsArray = [[NSMutableArray alloc] initWithArray:[[[defaults objectForKey:MYLOCATIONSARRAY_KEY] mutableCopy] autorelease]];
or just this
myLocationsArray = [[defaults objectForKey:MYLOCATIONSARRAY_KEY] mutableCopy];
I would suggest to set a breakpoint on the line where your program is throwing an exception (the one containing removeObjectAtIndex) and inspect with the debugger the real type of the array. If you go with you mouse over the array name, a popup menu will display giving you all the information you need about the pointed object.
What I expect is that you find out this way that the object is an NSArray (vs. NSMutableArray) and then trace back to the point where you initialized it in the first place.
It looks like you're working with NSUserDefaults. All objects you get out of NSUserDefaults are always immutable, regardless of what you stored into it. NSUserDefaults doesn't keep a reference to the specific object you set into it, it keeps the data. It's effectively making a copy. When you get something out of NSUserDefaults, it makes a new (immutable) object from the data it has stored and gives that to you.
Unsurprisingly, you can't change what's stored in NSUserDefaults by mutating what you (think you) stored in it. You can only change what's stored by replacing what you previously stored by storing something anew.
The declaration should not matter; your error is a run-time error. It sounds like your myLocationsArray variable has been assigned an immutable array (NSArray) though whether it is being re-assigned somewhere or was always immutable is impossible to say from your code fragment.

NSDictionary + ARC + copy vs reference

These are probably are pretty simple YES|NO type questions.
I have some NSDictionaries containing other NSDictionaries. Let's say NSDictionary_A and NSDictionary_B. These persist for the life of the app.
The NSDictionaries contained in NSDictionary_A are passed by reference to various objects:
track.instrument = [NSDictionary_A objectForKey:#"Blue"];
Later it gets changed:
track.instrument = [NSDictionary_A objectForKey:#"Red"];
So first question: The #property instrument is synthesized + retained as strong so does the setter for instrumentset the current value of instrument to nil before setting the new value, and if so, does this affect the source of the reference in NSDictionary_A - in other words, set the reference to nil'? Sounds wrong just writing it out.. so I think the answer is NO here. Also, it probably doesn't matter that the #property instrument is stored as weak or strong since the reference in NSDictionary_A1 persists for the app life but since it is a pointer, should be weak - YES?
Second question: An NSDictionary in NSDictionary_B is passed to an object but it can change some of the values in that NSDictionary:
track.playbackType = [NSDictionary_B objectForKey:#"Random"];
[track.playbackType objectForKey:#"maxRange"] = 20;
So should I be making a copy of the NSDictionary here because it's values will be changed or am I completely misunderstanding this whole reference passing thang?
You are getting mixed up in how pointers work.
For the first question, "track.instrument" is just a pointer. So it will start as "pointing to nil".
this:
track.instrument = [NSDictionary_A objectForKey:#"Blue"];
means, "stop pointing to nil and point to that object"
If you can ensure your dictionary will persist for the entire app then it doesnt matter, whatever is at #blue key will never get dealocated. But for the sake of having the correct code, it should be weak.
Edit: Had read the second question incorrectly.
Second question:
about this:
track.playbackType = [NSDictionary_B objectForKey:#"Random"];
first your pointer points to the NSDictionary from the dictionary.
[track.playbackType objectForKey:#"maxRange"] = 20;
Since it is a NSDictionary this is not valid. You cannot change NSDictionaries because they are immutable, it SHOULD be NSMutableDictionary.
HOWEVER if you are not interested in putting back the modified version into the original dictionary then you can copy it but as a NSMutableDictionary first, and then change it.
NSMutableDictionary *mutableDict = [[NSDictionary_B objectForKey:#"Random"] mutableCopy];
track.playbackType = mutableDict; //Note how track.playbackType has to be NSMutableDictionary aswell.
VERY IMPORTANT: Since you are creating a "new" dictionary. track.playbackType has to be strong, or it will simply get instantly dealocated after the function ends and mutableDict gets out of scope.
References are just pointers, setting one to nil will have no effect except in the following case: It is the last strong reference and other weak references still exist. In that case all the weak references will become nil. Strong properties will set the old value to nil, in effect sending a release call but this affects the REFERENCE, not the CONTENT of the reference.
As for the second question, it is quite confusing and I need more info about playbackType. You say it is an NSDictionary but NSDictionary doesn't have the property maxRange so it must be a type that you defined. You can't change the values of an NSDictionary either because it is immutable.
But here is a generic answer: If you pass a pointer to a mutable object as strong (or weak even) you will be able to change the content of the original. If you pass a pointer to a mutable object as a copy you will get a new object that doesn't affect the original.