I'm working on iPhone and I'm using navigation.
I have list of data in RootViewController, and pass one data to detailViewController when a cell is clicked. Like this,
detailViewController.message = [tableData objectAtIndex:indexPath.row];
And I want to modify the content of 'message' in detailViewController. Is that possible?
I've tried that, but I get an error that it's immutable object. How can I do that? Somebody give me a hint. Thanks ;)
Added ------------------------------------------------
Ok. I'll specify the question.
in detailViewController, the message is decleared NSMutableDictionary* type.
and used like this.
NSMutableString *str = [m_message objectForKey:KEY_CONTENT];
[str appendFormat:#"appended!"];
And I've got this message.
* Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Attempt to mutate immutable object with appendFormat:'
Can't I just modify it's content like modifying data in c++ using pointer?
I think it would be best to use a delegate pattern for this. Create a DetailViewControllerDelegate protocol that informs its delegate of a changed message. Something along the lines of:
-(void)detailViewController:(DetailViewController *)controller didChangeMessage:(NSString *)message;
You could also directly manipulate the object by using a mutable one (NSMutableString, NSMutableArray etc.), but using a delegate improves reusability and decouples your classes.
EDIT:
The fact that your dictionary is mutable doesn't mean the object inside it are mutable as well. Your string str is probably immutable. Since NSMutableString is a subclass of NSString, the assignment will work though. You should make sure that you put a mutable string in the dictionary, or use the mutableCopy method of NSString to get a mutable copy of it.
It depends on what type of object you are passing. Immutable means that you cant alter the object. If you for exaple use NSArray, you have to switch to NSMutableArray instead. The same for string or dictionary.
Okay, i think i figured it out. The documentation for appendFormat says that appendFormat is for appending objects, just like when you use stringWithFormat.
The documentation states that:
The appended string is formed using
NSString's stringWithFormat: method
with the arguments listed.
You should use appendString to get the result you want.
Related
Can someone explain to me why this doesn't work:
CoreDataClass *classObject = (CoreDataClass *)[some method that returns a dictionary with exact KVC pairs that match CoreDataClass];
NSString *myString = classObject.stringProperty;
But this does:
CoreDataClass *classObject = (CoreDataClass *)[some method that returns a dictionary with exact KVC pairs that match CoreDataClass];
NSString *myString = [classObject valueForKey:#"stringProperty"];
EDIT:
What's the easiest way to cast the dictionary as my NSManagedObjectClass CoreDataClass so I can access properties directly?
It doesn't work since KVC compliance is not at all what defines classes or makes them castable - the class hierarchy exists for a reason, and just ensuring adherence to certain methods doesn't magically make something an instance of a completely different class. Keep in mind that the dot-accessor syntax is just sugar for a method send, so these two are equivalent:
classObject.stringProperty
[classObject stringProperty]
...and the latter obviously isn't valid for instances of NSDictionary (i.e. [[NSDictionary class] instancesRespondToSelector:#selector(stringProperty)] is NO).
Your latter example works because of the very premise of your question: if something is KVC-compliant for the key stringProperty, and you ask it for a value for that key, then obviously you get something back. Furthermore, both NSDictionary and CoreDataClass respond to the selector -valueForKey:, so the message send actually works at runtime.
The best way to get the two across isn't a "cast" at all - it's a complete conversion, at the property level, of the data involved. You might consider creating a custom -initWith... method on CoreDataClass that lets you instantiate its properties from a dictionary, or finding a way to get your method to return an actual instance of CoreDataClass instead of an NSDictionary.
Note that this solution may differ from the "easiest" way to get the data across, which is effectively to keep doing what you're doing and use -valueForKey: (though preferably without the cast, which is misleading).
Casting objects only appears to work (in the sense that you won't get type-checking errors) because it's a hint to the compiler, but it doesn't actually change anything about what the pointer points to, so you are still pointing to an NSDictionary. This is because, at the end of the day, you are essentially casting a pointer to a pointer, but telling Xcode that you are allowed to send a different set of selectors to it.
For NSManagedObjects, creation from a dictionary depends on a few things, but the recommended way is to make a class method on your custom class which will use NSEntityDescription and you NSManagedObjectContext, and sets the properties from the dictionary to the object:
+(CoreDataClass *) coreDataObjectWithDictionary:(NSDictionary *) spec {
CoreDataClass *myInstance = [NSEntityDescription insertNewObjectForEntityForName: #"CoreDataClass" inManagedObjectContext: [myMOCProvider sharedMOC];
myInstance.someProp = [spec valueForKey:#"someProp"];
}
So, I'm fairly certain that if I plan on manipulating strings often, such as with stringByAppendingString, I should be using variables of type NSMutableString.
But what if I'm doing something like this?
UILabel *someLabel = [[UILabel alloc] init];
[someLabel setText: [[someDictionary objectForKey:#"some_key"] stringByAppendingString:#"some other string"];
I read that if you use stringByAppendingString on an NSString, you end up with leaks because the pointer associated with the initial NSString moves around, pointing to the new string created by the append, whereas with NSMutableString, your pointer always points to that mutable string.
So my question is, what is implicitly happening when I call stringByAppendingString on something that is a string, but not explicitly an NSString or an NSMutableString? Such as, in my above case, the value of some key in a dictionary. Is doing this wrong, and should I be doing something like below?
[[[NSMutableString stringWithString:[someDictionary objectForKey:#"some_key"]] stringByAppendingString:#"some other string"]]
I read that if you use
stringByAppendingString on an
NSString, you end up with leaks
because the pointer associated with
the initial NSString moves around,
pointing to the new string created by
the append, whereas with
NSMutableString, your pointer always
points to that mutable string.
That sounds like the advice of someone who didn't quite have a grasp of what is going on with the memory management. Sure, [NSString stringByAppendingString] returns a new string. But what you do with that new string is up to you. You could certainly cause a memory leak by reassigning the result to a retained property in a careless fashion, like so:
myStringProperty = [myStringProperty stringByAppendingString:#" more bits"];
The correct form would be to use self, like so:
self.myStringProperty = [myStringProperty stringByAppendingString:#" more bits"];
Follow the cocoa memory guidelines.
As for dictionaries and other collection types: treat what comes out of the dictionary appropriately given the type you know it to be. If you pull an object out which is actually an NSString, but try to use it as a NSMutableString, your app will fall over (with 'selector not found' or similar). So in that case, you do need to make a new NSMutableString from the NSString.
Interesting note: Apple chose to make NSMutableString a subclass of NSString. Something about that seems unwise to me -- if something looks to be immutable, because it has type NSString, I want it to be immutable! (But in fact it could be NSMutableString.) Compare that to Java, which has a String class and a completely separate BufferedString class.
I've always been a fan of [NSString stringWithFormat#"%#%#", a, b]; because then you clearly get a new autoreleased string and can dispose of "a" and "b" correctly.
With [someDictionary objectForKey:#"some_key"], you will be getting the type of object that was put into that dictionary originally. So blindly calling stringByAppendingString without knowledge of what's in that dictionary seems like a bad idea.
-stringByAppendingString is going to return you a new NSString that is distinct from both strings involved. In other words:
NSString *string3 = [string1 stringByAppendingString:string2];
string3 is an entirely new string. string1 isn't changed at all, nothing happens to its memory location or contents. The person who told you that probably just misunderstood what was going on.
[mutableString1 appendString:string2];
In this case, mutableString1 still points at the same object, but the contents of that object have been altered to include string2.
One last thing to keep in mind is that if you are using mutable strings, you should be careful with sharing references to it. If you pass your mutable string to some function which keeps a pointer to that mutable string and then your code changes that mutable string at some point in the future, the other reference is pointing at exactly the same object which means the other code will see the change as well. If that's what you want, great, but if not you must be careful.
One way to help avoid this problem is to declare your #property statements for NSStrings to be "copy" instead of "retain". That will make a copy of your mutable string before setting it in your property and the -copy method implicitly gives you a NON-mutable version, so it'll create an NSString copy of your NSMutableString.
If you follow the rules for memory management, you will be fine using stringByAppendingString. In a nutshell:
if you own an object, you need to release or autorelease it at some point.
you own an object if you use an alloc, new, or copy method to create it, or if you retain it.
Make sure you read up on Apple's Memory Management Rules.
In the first code sample in your question, you aren't using alloc, new, copy or retain on any of the NSStrings involved, so you don't need to do anything to release it. If outside of the code that you've included in the sample you are using alloc, new, copy or retain on any NSStrings, you would need to ensure that they are released later.
I am having a class called Customer.
Customer *object; //in this object i have the following data varialbles.
object.customerName
object.customerAddress
object.customerContactList
I declared customerContactList as NSMutableArray and also allocated and initialized.
Now I am adding or deleting from contactList.
//Adding.
[object.customerContactList addObject:editcontacts];// here editcontacts one of the object in contactList.
//Deleting.
[object.customerContactList removeObjectAtIndex:indexPath.row];
[theTableView deleteSections:[NSIndexSet indexSetWithIndex:indexPath.section] withRowAnimation:UITableViewRowAnimationFade];
I am getting exception even it is NSMutableArray. Please help me.
Thank You,
Madan Mohan
I'm jut guessing, because as Williham Totland said, we need to see your code. How is the customerContactList property declared, implemented and assigned?
My guess is that somewhere you are using copy on the array. If you send copy to an NSMutalbeArray you'll get an immutable copy back. If your property is declared:
#property (copy) NSArray* customerContactList
you'll get an immutable array back when you use it. You might even get an immutable array if you declare it thus (I'm not sure how clever the compiler is):
#property (copy) NSMutableArray* customerContactList
You keep asking this question, and you keep getting the same answer: "Izit?". Before you can be helped any further on this point, you need to actually show some more code, like the interface declaration for Customer.
What you should do, at this juncture, is to add a method to Customer along the lines of - (void)addToContactList:(id)contact. Accessing collection objects directly on an object in the manner you are describing is hardly considered kosher.
And another thing: your properties really don't need to have the class name in their name; rather than Customer *object; object.customerName; you should have Customer *customer; customer.name; and so on. Makes for far more readable code.
And this bears repeating: You cannot get that exception if you have an NSMutableArray. That exception is unequivocal proof that what you have, in fact, is not an NSMutableArray; and cannot be. This is the function of the exception in question. This is what the exception is, does, and means. This is the essence of the exception, it's reason for being, or raison d'etre, if you will. In case it wasn't clear: The array you are attempting to add to is not mutable. It is immutable.
I am trying to update data in a table view using a NSMutableArray. Quite simple :(
What is happening is that I get my data from a NSURLConnection Callback, which I parse and store it in an array and call reload data on the table view. The problem is that when cellForRowAtIndexPath is called back by the framework. The array still shows the correct count of the elements but all string objects I had stored earlier are shown as invalid.
Any pointers
Maybe your problem is something like the below
NSString *value = [NSString stringWithFormat:#"%#",stringFromWebService];
[arrayOfObjects addObject:value];
[value release]; // This should not be released
This may not be exact but could be similar. Here value is obtained from class method of NSString which is an autoreleased object so if you can see it will be released twice, one by autorelease and other by you.
This is the area you need to check.
Check that you are retaining the NSString objects in your NSURLConnection callback. Are you autoreleasing them?
Edit
Ok, forget that last thing. Double checking myself, NSMutableArray will automatically retain the objects when you add them to your array. So you won't need to retain them explicitly:
Like NSArray, instances of
NSMutableArray maintain strong
references to their contents. If you
do not use garbage collection, when
you add an object to an array, the
object receives a retain message. When
an object is removed from a mutable
array, it receives a release message.
So you need to check you aren't doing any other explicit releases on the objects you are adding to the array. Are they referenced anywhere else?
The problem is there where you are adding the string object to a mutable array. The string object was already invalid that time. That's why the time you are accessing them from the array, they are invalid or do not exist.
So best thing is to check the code where you are adding the string object during the parsing.
Your problem may be that you have not initiated the array correctly.
Are you using the array as an attribute of the class?
Make sure when you load the view you allocate and initiate the array.
I want to add a selector into a dictionary (the main purpose is for identifying the callback method and delegate after finish doing something)
But I find that I can not do that, the program will get an error "EXC_BAD_ACCESS".
Are there any other way for me to add that method selector to a dictionary?
Thanks for your help.
I know this question was answered a long time ago, but just in case anyone stumbles upon it like I did...
The combination of NSStringFromSelector and NSSelectorFromString as answered above is probably the best way to go. But if you really want to, you can use a selector as a value or key in an NSDictionary.
A selector (type SEL) is implemented as a pointer to a struct in Apple's Objective-C runtimes. A pointer cannot be used directly in a dictionary, but a pointer can be wrapped in an NSValue object that can be used.
Using this method you can store a selector as a value in a dictionary using code like this:
dictionary =
[NSDictionary dictionaryWithObject:[NSValue valueWithPointer:selector]
forKey:key];
A selector can be retrieved using code like this:
SEL selector = [[dictionary objectForKey:key] pointerValue];
Similarly for using a selector as a key:
dictionary =
[NSDictionary dictionaryWithObject:value
forKey:[NSValue valueWithPointer:selector]];
value = [dictionary objectForKey:[NSValue valueWithPointer:selector]];
Adding a new entry to a dictionary does two things (in addition to adding it to the dictionary, obviously):
It takes a copy of the key value. This means that the the key object must implement the NSCopying protocol
retains the value. This means that it needs to implement the NSObject protocol
It's probably the second that's causing your EXC_BAD_ACCESS.
There are at least two ways around this.
Firstly, rather than adding the selector you could add the instance of the class that implements the selector to your dictionary. Usually your class will inherit from NSObject and it will work fine. Note that it will retain the class though, maybe not what you want.
Secondly, you can convert a selector to a string (and back again) using NSSelectorFromString and NSStringFromSelector (docs are here).
I get my answer based on the comment of Zydeco:
You can convert between SEL and
NSString using NSSelectorFromString
and NSStringFromSelector
The common idiom in Obj-C is to have specific names for callbacks for specific events. (Such parserDidBeginDocument: from NSXMLParserDelegate). If you really need to be able to specify the names, it is likely that your only recourse is to add the names of the selectors as #"mySelector:withArgument:context:" or somesuch.