Why doesn't this property update in UI? - objective-c

I have a simple app: A text box bound to item.title. This property is exposed as a NSString, but internally is a NSMutableString.
The problem: Changes to the title property are not reflected in the UI.
Here is the implementation of item:
#interface Item : NSObject
{
NSMutableString *_title;
}
#property NSString *title;
#end
And the implementation:
#implementation Item
-(void)mutateTitle
{
[self willChangeValueForKey:#"title"];
[_title appendString:#"mutated"];// this does not work
//_title = [[NSMutableString alloc] initWithString:#"new instance"];// this works
[self didChangeValueForKey:#"title"];
}
-(NSString *)title
{
NSLog(#"get title returning: %#", _title);
return _title;
}
-(void)setTitle:(NSString *)title
{
_title = [[NSMutableString alloc] initWithString:title];
}
#end
When a button is pressed, we send mutateTitle to the item, which changes the title, and notifies KVO of the property change.
The UI does respond to the change, and when title is called, we return the correct string.
But the UI does not reflect the changes.
Note that if I un-comment the line where I assign a new instance to _title, the update happens fine.
Also, if title returns a copy of _title, it also works fine.
Why is this happening? Is there a way I can make this work as I want?
*edit: title is being assigned elsewhere in the code; it is not nil.

That's because if the mutable string mutates, but the property doesn't change (the pointer still points to the same object), then KVC will not consider it a real change. That is probably done for efficiency reasons (in case that didChangeValueForKey: is called but the string is the same, there's no need to update the UI).
I suggest to don't use a mutable string at all, it doesn't fit to your purpose. Just use a normal string, and instead of mutating it reassign it:
-(void)mutateTitle
{
[self willChangeValueForKey:#"title"];
_title= [_title stringByAppendingString: #"mutated"];
[self didChangeValueForKey:#"title"];
}

Related

Objective-c - NSMutableString setString vs NSString

Is it a better practice, in a setter, to retain and release NSString as follows :
-(void) setName:(NSString *)newName
{
if(newName != nil)
{
[newName retain]:
[m_Name release];
m_Name = newName; //Where m_Name is a NSString *
}
//I'm not sure for this code, I have difficulties understanding memory-management in ObjC
}
Or to change the value via a NSMutableString :
-(void) setName:(NSString *)newName
{
if(newName != nil)
[m_Name setString:newName]; //Where m_Name is a NSMutableString *
}
If any or both of the methods are incorrect(s), let me know.
A couple of thoughts:
Best practice is to not write setters at all, to take advantage of the automatically synthesized accessor methods). Writing your own is only an opportunity to mess up your memory management or introduce a bug. You should have a compelling need for a custom setter before you write one.
The emerging convention for instance variable names is to use the property name preceded by a leading underscore (e.g., for a property called name, the ivar is _name). If you omit the #synthesize statement, the compiler included with recent versions of Xcode will do this automatically for you.
The question of what the setter should be makes no sense in the absence of your stating what memory qualifiers your property had. I'm going to assume you defined your property as retain.
Changing the property to a NSMutableString changes the behavior of your property and I would not advise that unless you really needed a mutable string for some reason.
Your first example does nothing if someone sets the name property to nil. But if someone wants to set it to nil, you should still (a) release the old name value; and (b) set your ivar to nil. (By the way, my code below takes advantage of the fact that sending a message to a nil object does nothing, so I don't need to check if it's nil or not, in this case.)
So, I assuming you have a property defined as follows:
#property (nonatomic, retain) NSString *name;
and a synthesize line that is either omitted or looks like:
#synthesize name = _name;
Then, I think the setter would look like:
-(void) setName:(NSString *)name
{
// if you want to program defensively, you might want the following assert statement:
//
// NSAssert(name == nil || [name isKindOfClass:[NSString class]], #"%s: name is not string", __FUNCTION__);
if (name != _name)
{
[_name release];
_name = name;
[_name retain];
}
}
By the way, I assume your init method properly initializes _name and that dealloc releases it.
- (id)init
{
self = [super init];
if (self) {
_name = nil;
}
return self;
}
- (void)dealloc
{
[_name release];
[super dealloc];
}
As bblum points out, it's prudent to use copy for your NSString properties:
#property (nonatomic, copy) NSString *name;
Then, I think the setter would look like:
-(void) setName:(NSString *)name
{
// if you want to program defensively, you might want the following assert statement:
//
// NSAssert(name == nil || [name isKindOfClass:[NSString class]], #"%s: name is not string", __FUNCTION__);
if (name != _name)
{
[_name release];
_name = [name copy];
}
}
But really, you simply shouldn't be writing setters unless you absolutely need to.
Finally, your code has a comment about finding memory management confusing. While you definitely need to understand it, I'd make two final suggestions:
Consider using Automatic Reference Counting (ARC). While this doesn't obviate the need to understand how memory management works (see the Advanced Memory Management Programming Guide), it does make it easier to write code that handles memory management properly. If you write manual reference counting (MRC) code, it's very easy to make simple memory management mistakes that ARC would otherwise take care of for you.
Especially if you're going to use MRC, avail yourself of Xcode's static analyzer ("Analyze" on the "Product" menu or press shift+command+B). This can facilitate finding many routine memory management issues that plague MRC code. The Finding Memory Leaks section of the Instruments User Guide also illustrates how to find leaks while debugging your code, but often the static analyzer can identify issues just by examining your code.
The first solution is better. This is how retain properties are handled. You retain the new value and then release the old value. Also if the nil case is not crucial to be handled, you can depend on the default implementation, generated by #synthesize.
For the second solution, it's really unnecessary, and it's a little bit against convention. Also, in this solution, you have to initialize m_name before ever assigning any string to it. You can do that in init.
- (void) init {
if (self = [super init]) {
m_name = [[NSMutableString alloc] init];
}
}
Conclusion: First solution is definitely better.

NSLog returning null instead of string

NSLog is returning the output 'Null" instead of a string that I would have expected. I suspect that this is a problem with private instance variables and such, but since I am not familiar with Object-oriented programming I cannot determine the cause.
//The viewDidLoad method in MainGameDisplay.m:
- (void)viewDidLoad
{
[super viewDidLoad];
Engine *engine = [[Engine alloc] init];
[engine setPlayerName: viewController];
}
The string is entered by a UITextField, the property being
//ViewController.h
#property (strong, nonatomic) IBOutlet UITextField *PlayerNameTextView;
The method works fine and returns the correct string if [engine setPlayerName: self] is placed into ViewController, but anywhere outside the location that *PlayerNameTextView is causes this problem.
//Engine.m
#implementation Engine
{
ViewController *firstPage;
}
NSString *Player;
-(void) setPlayerName: (ViewController *) name
{
Player = [[name PlayerNameTextView] text];
NSLog(#"%#", Player);
}
NSLog return type is void as you can see in it's documentation. There is no reason to expect any return value for a call to it, since it does not return anything.
Make sure that 'name' is properly initialized. Try putting an assert(name != nil) right before the NSLog. Or better yet, set a breakpoint at the NSLog and inspect the variables.
Another suggestion: Why not make the method -(void) setPlayerName:(NSString*)name? This is more straightforward than passing around pointers to view controllers, and would be easier to debug.

Does iOS change reference of objects?

I don't have too much experience with iOS, but I am working on some legacy code. In the project, we use an object as the key of a dictionary:
NSMutableDictionary * dict;
RoleContainer * role = [Class getRole];
[dict setObject:[Class getData] forKey:role];
We have the role passed to another function. When we try to retrieve the data:
data = [dict objectForKey:role];
Sometimes the return value is empty. It happens about 10% of time. I stepped through the code and found out after passing role to the function the reference of the "role" object had been changed! For example, from 0x002bf500 to 0x00222bad.
Why?
In order to play nicely with NSMutableDictionary your RoleContainer class must implement a hash and isEqual methods. Otherwise, equal roles may get recorded twice in the dictionary, or querying by a valid key may fail.
Here is a brief sample of how you could implement your hash/isEqual when your class has an identifying member:
#interface RoleContainer : NSObject
#property (nonatomic, readonly) NSString *name;
- (NSUInteger)hash;
- (BOOL)isEqual:(id)anObject;
#end
#implementation RoleContainer
#synthesize name = _name;
- (NSUInteger)hash {
return [name hash];
}
- (BOOL)isEqual:(id)anObject {
if (![anObject isKindOfClass:[RoleContainer class]]) {
return NO;
}
RoleContainer *other = (RoleContainer*)anObject;
return [_name isEqual:[other name]];
}
#end
In that code, dict is going to be nil, so you're sending messages to nothing. Is it actually pointing to something later on?
I'm assuming your RoleContainer responds to the stringValue method, which might be a good place to look at what is going on if it's overloaded.
If it's using the standard string value, then it's returning the class and memory location. This may not be reliable if someone down the line is resetting objects to keys.
You may also have an issue where the getData object is being released somewhere where it shouldn't be touched. Try enabling NSZombieEnabled in the debugger, or enable ARC.
try
NSMutableDictionary * dict = [NSMutableDictionary dictionary];
Also it would make sense to have a look at your Class. Is it a class, and getRole a class-method? is it an object?

Property attribute "retain" doesn't seem to be working?

I've implemented a bit of code from one of the many Apple code examples, but I'm having a bit of trouble, because the retain attribute of one of the properties doesn't appear to be working. Here's the property declaration:
#property (nonatomic, retain) EditingViewController *editingViewController;
And here's the code:
- (EditingViewController *)editingViewController {
// Instantiate the editing view controller if necessary.
if (editingViewController == nil) {
EditingViewController *aController = [[EditingViewController alloc] init];
editingViewController = aController;
[aController release];
}
return editingViewController;
}
I understand that (retain) is supposed to cause the retain count to increase by 1 on assignment; however, the code fails unless I do send [aController retain] myself, or don't send [aController release]. What am I missing here?
When you reference editingViewController, it is equivalent to self->editingViewController, i.e. an access to an ivar.
If you want to use a getter or setter, you need to use self.editingViewController, or equivalently [self setEditingViewController:aController].
This is why I prefer to use an ivar with a different name to the property, for example:
EditingViewController* i_editingViewController;
#property (nonatomic, retain) EditingViewController *editingViewController;
#synthesize editingViewController = i_editingViewController;
Then you can write your lazy getter as:
- (EditingViewController *)editingViewController {
// Instantiate the editing view controller if necessary.
if (i_editingViewController == nil) {
i_editingViewController = [[EditingViewController alloc] init];
}
return i_editingViewController;
}
or
- (EditingViewController *)editingViewController {
// Instantiate the editing view controller if necessary.
if (i_editingViewController == nil) {
EditingViewController *aController = [[EditingViewController alloc] init];
self.editingViewController = aController;
[aController release];
}
return i_editingViewController;
}
I would probably use the former method (not invoking the setter) because the value of editingViewController (as seen by any observer) has not really changed, but either way should work fine and the different name (for ivar and property) help avoid the confusion or accidental misused. It is also a mild encouragement to use the property (since it avoids the slightly ugly prefix).
Note that Apple reserves the _ prefix, and that setters and getters should not be used in the init/dealloc routines.
You have to write self.editingViewController in order to use the property. Just "editingViewController" is a direct access to the Class member variable, whereas self.editingViewController is equivalent to [self setEditingViewController:...] and will do the appropriate retain/release job.

NSCollectionView draws nothing

I'm trying to set up an NSCollectionView (I have done this successfully in the past, but for some reason it fails this time).
I have a model class called "TestModel", and it has an NSString property that just returns a string (just for testing purposes right now). I then have an NSMutableArray property declaration in my main app delegate class, and to this array I add instances of the TestModel object.
I then have an Array Controller that has its Content Array bound the app delegate's NSMutableArray. I can confirm that everything up to here is working fine; NSLogging:
[[[arrayController arrangedObjects] objectAtIndex:0] teststring]
worked fine.
I then have all the appropriate bindings for the collection view set up, (itemPrototype and content), and for the Collection View Item (view). I then have a text field in the collection item view that is bound to Collection View Item.representedObject.teststring. However NOTHING displays in the collection view when I start the app, just a blank white screen. What am I missing?
UPDATE: Here is the code I use (requested by wil shipley):
// App delegate class
#interface AppController : NSObject {
NSMutableArray *objectArray;
}
#property (readwrite, retain) NSMutableArray *objectArray;
#end
#implementation AppController
#synthesize objectArray;
- (id)init
{
if (self = [super init]) {
objectArray = [[NSMutableArray alloc] init];
}
return self;
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
TestModel *test = [[[TestModel alloc] initWithString:#"somerandomstring"] autorelease];
if (test) [objectArray addObject:test];
}
#end
// The model class (TestModel)
#interface TestModel : NSObject {
NSString *teststring;
}
#property (readwrite, retain) NSString *teststring;
- (id)initWithString:(NSString*)customString;
#end
#implementation TestModel
#synthesize teststring;
- (id)initWithString:(NSString*)customString
{
[self setTeststring:customString];
}
- (void)dealloc
{
[teststring release];
}
#end
And then like I said the content array of the Array Controller is bound to this "objectArray", and the Content of the NSCollectionView is bound to Array Controller.arrangedObjects. I can verify that the Array Controller has the objects in it by NSLogging [arrayController arrangedObjects], and it returns the correct object. Its just that nothing displays in the NSCollectionView.
UPDATE 2: If I log [collectionView content] I get nothing:
2009-10-21 08:02:42.385 CollViewTest[743:a0f] (
)
The problem is probably there.
UPDATE 3: As requested here is the Xcode project:
http://www.mediafire.com/?mjgdzgjjfzw
Its a menubar app, so it has no window. When you build and run the app you'll see a menubar item that says "test", this opens the view that contains the NSCollectionView.
Thanks
The problem is that your not correctly using KVC. There is two things you can do.
Method 1: Simple but not so elegant
Use the following code to add the object to the array
[[self mutableArrayValueForKey:#"objectArray"] addObject:test];
This isn't so elegant as you have to specify the variable using a string value, so you will not get compiler warnings when spelt incorrectly.
Method 2: Generate the KVO methods needed for the array "objectArray".
Select the property in your interface declaration
Select Scripts (the script icon in the menubar) > Code > Place
accessor decls on Clipboard
Paste the declarations in the
appropriate spot in your interface file
Select Scripts > Code > Place
accessor defs on Clipboard
Paste the definitions in the
appropriate spot in your implementation file
You can then use a method that looks like
[self insertObject:test inObjectArrayAtIndex:0];