override description or stringValue in cocoa? - cocoa

I want to have an descriptive string for an object in Cocoa. I'm thinking about overriding either the description method or the stringValue method. Which is preferable and why? The only guideline I could find was in here stating
You are discouraged from overriding description.
Is this indeed what you would suggest? Any other preferred overrride point?

I personally override description in virtually all subclasses I create. I guess, like Tom Duckering writes in his comment, that your quote only applies to Managed Objects.
- (NSString *)description
{
return [NSString stringWithFormat:#"%# <%p>", NSStringFromClass([self class]), self];
}

description is the way to go, that's what it's called to supply string representation of an object.
- (NSString*)description
{
return [NSString stringWithFormat:#"%#, %#; %#", a, b, c];
}
I believe suggested by Hillegass' book as well.

To answer your question from the other direction, stringValue is something altogether different—it doesn't describe the receiver, it's a property of it. Your custom description may even include the stringValue, or an excerpt of it if it's long.
A key difference is that stringValue is often a mutable property (see, for example, that of NSControl), whereas description is always an immutable property, computed upon demand.

You can also override [NSObject debugDescription] which is called by the debugger. It's what is called when use "print to console" in the debugger. You can also call it directly in a NSLog.
By default in most classes debugDescription just calls description but you can make them return separate strings. It's a good place to load up the output with details.
Categories are a good place to park the method for both your custom classes and existing classes. This is especially useful because you can include the category in a debug build but exclude it in the release. If the category is not present, the code calls the default class method instead.
I have a debugging category for UIView that dumps out every attribute I could think of. If I hit a nasty bug I just include the category and then I can see everything about every view right in the debugger console.

Related

Detect attempt to insert nil on a dictionary and log a backtrace

Is there a way I can detect an attempt to insert a nil value on a dictionary and log a backtrace on my application ?. I know how to do it with Xcode, but the error occurs only with some users. Hence I need to send them a new build that hopefully would log a backtrace of the attempted nil insertion.
This is probably because an image or a font is not being loaded correctly, if there is another way to find out I would also like to know.
You can't do this with regular NSMutableDictionary objects, as adding a nil value is legal.A workaround would be to use a custom dictionary implementation that wraps a NSDictionary instance and forwards all methods to the wrapped objects; and in the case of setObject:forKey: (or setValue:forKey:) makes a check and logs the backtrace if the value is nil. The downside is that you'll have a lot of boiler plate code to write. You can reduce the boiler plate code size if you implement only the methods needed by your code.
Another approach would be to use method swizzling and replace the setObject:forKey: and setValue:forKey: with your method that firstly checks the value and if OK forwards the call to the original method. However NSDictionary being a class cluster you might experience problems with this approach.
Update. Just thought of a 3rd solution: add a category over NSMutableDictionary with getters/setters for the keys you're interested in, and update your code to call those setters instead of the setObject:forKey: method.
As I understand your problem you have failed to check the result when loading an image, font or something similar and this is causing an error when the bad result is later inserted into a dictionary. What you are after is a quick way, as you have a large codebase, to track down that insertion so you can backtrack and find the source of the problem and add appropriate checking code to the load/whatever.
What you can do is:
Replace NSMutableDictionary with a simple class, say DebuggingDictionary, which appears to be (explained below) a derived class and just checks for nil on insertion and produces the diagnostics you are after; and
Do a find/replace over your code base for [NSMutableDictionary alloc] and replace with [DebuggingDictionary alloc]. You can easily change this back once the problem has been fixed.
So how to write DebuggingDictionary?
Well as NSMutableDictionary is a class cluster you cannot just derive from it and override setObject:forKey:, you have provide your own storage for the keys & objects and override six key methods and all (or at least all you use) of the init methods.
Sounds bad but it isn't. First read this answer to a different but related question. In that answer a version of NSMutableArray is created which checks the type of elements added, you need to check whether the items are nil. The implementation provides the storage by wrapping a real NSMutableArray. You can do the equivalent with NSMutableDictionary, the documentation (NSMutableDictionary and NSDictionary) lists the six primitive methods you need to override.
That answer also adds its own initWithClass: initialisers and blocks the standard ones, you just need to implement the standard dictionary ones - by calling them on the wrapped dictionary.
[Minimal checking in the following code sketches, all typed directly into answer so beware of typos]
So for example initWithCapacity: becomes something like:
- (instancetype) initWithCapacity:(NSUInteger)numItems
{
realDictionary = [NSMutableDictionary dictionaryWithCapacity:numItems];
return self;
}
and the core insertion method becomes:
- (void)setObject:(id)anObject forKey:(id<NSCopying>)aKey
{
if (anObject == nil)
{
// produce your diagnostics
}
else
realDictionary[aKey] = anObject;
}
Once you've tracked your problem to its source and fixed it there just remove your DebuggingDictionary and find/replace all occurrences in your code with NSMutableDicitionary.
HTH
You could create subclass of NSAplication and override method reportException
Use
+[NSThread callStackSymbols];
or
-[NSException callStackSymbols];
to get a backtrace. You can print a backtrace using NSLog.
You may find also Apple's example useful for you:
ExceptionReporting:
Demonstrates how to show a customized exception reporting user interface.
This lets the user know when the exception happens in order to possibly prevent
subsequent random crashes that are difficult to debug.
Instead of setting key/value directly to the dictionary, how about using a method that accepts parameters that should be inserted into the dictionary and tests each for nil before adding it to the dict?
-(void)addKeyAndValueToDict:(NSString*)aKey andValue:(NSString *)aValue {
if ( aValue == nil ) {
NSLog(#"value was nil for key: %#", aKey);
return;
}
[self.someDict setValue:aValue forKey:aKey];
}

implementing custom accessor methods

I am reading "Core Data Programming Guide". It contains this text:
You must, however, change attribute values in a KVC-compliant fashion.
For example, the following typically represents a programming error:
NSMutableString *mutableString = [NSMutableString stringWithString:#"Stig"];
[newEmployee setFirstName:mutableString];
[mutableString setString:#"Laura"];
For mutable values, you should either transfer ownership of the value
to Core Data, or implement custom accessor methods to always perform a
copy. The previous example may not represent an error if the class
representing the Employee entity declared the firstName property
(copy) (or implemented a custom setFirstName: method that copied the
new value). In this case, after the invocation of setString: (in the
third code line) the value of firstName would then still be “Stig” and
not “Laura”.
Question regarding text: "In this case" is which case--the one where property is declared as "copy" or when its not?
Question regarding copy and programming practice:
From what I have read here:
NSString property: copy or retain?
I understand
that using copy will ensure that firstName is "Stig", not Laura
it is wise to do so because "in almost all cases you want to prevent mutating an object's attributes behind its back"
I would really like to know what is the above quoted text trying to tell us in the context of Core Data. We have to use "copy" anyway whether using Core Data or not. Also, I would be glad if someone could throw more light on point "2" (it is wise to...) above as in what will be the consequences of mutating an object's attributes behind its back?
your "Question regarding text: "In this case" is which case--the one where property is declared as "copy" or when its not?"
mis-matched the point that Apple document wants to explain, I believe.
As Apple document points out, if custom-accessor-method is implemented normally, the default implementation does NOT copy attribute values. If the attribute value may be mutable and implements the NSCopying protocol (as is the case with NSString, for example), you can copy the value in a custom accessor to help preserve encapsulation (for example, in the case where an instance of NSMutableString is passed as a value).
Here is a copying setter snippet
#interface Department : NSManagedObject
{
}
#property(nonatomic, copy) NSString *name;
#end
#implementation Department
#dynamic name;
- (void)setName:(NSString *)newName
{
[self willChangeValueForKey:#"name"];
// NSString implements NSCopying, so copy the attribute value
NSString *newNameCopy = [newName copy];
[self setPrimitiveName:newNameCopy];
[self didChangeValueForKey:#"name"];
} #end
The issue is when to use (and how) immutable values.
Since core data use KVO heavily when detecting changes done to objects, if you use a mutable property that is changed directly through it object and not through the property, CoreData will not detect the change to the object and your changes might not persist to the store.
If you use mutable NSManagedObject attributes, override the setter/getter method and use only them to mutate the underlying object (this mean that you are responsible to let CoreData know that a change did happen to the object, and it must be persisted to the store.
Also, if you use transformable properties for complex objects, you must trigger the change notifications yourself in order for CoreData to realise that a change has occurred, and the object should be re-transformed and saved when the context saves.
I would highly recommend that when it comes to simple objects like strings, you use immutable property values which will force you to go through the object properties and trigger the default KVO notification (copy attributes will also force the KVO notifications).

Why does the name of my internal member variable break data binding?

So I am really new to cocoa programming. In fact I am very new to the Mac platform too. Still trying to get used to the fact that control+left arrow takes me to the beginning of the line.
Ok:
So I am working through the tutorials in the book 'Cocoa Programming (4th edition) by Hillegass). So I got to chapter 9, which walks through creating a document view app, that uses a NSArrayControler to bind to a NSMutableArray of Person's.
The tutorial walked me through creating a sub-class of document, and adding a NSMutableArray pointer. So I took some liberty and named it mEmployee's instead of just employees.
#interface RMDocument : NSDocument
{
NSMutableArray* mEmployees;
}
-(void) setmEmployees:(NSMutableArray*)a;
-(void) insertObject:(Person*)p inEmployeesAtIndex:(NSUInteger)index;
-(void) removeObjectFromEmployeesAtIndex:(NSUInteger)index;
-(void) startObservingPerson:(Person*) person;
-(void) stopObservingPerson:(Person*) person;
#end
Now when I did this, it seems the binding broke on the NSArrayController. So methods like setEmployee, insertObject and removeObject were never called.
Now I am still very new to objective-C, but I thought that mEmployee's was an internal member variable to my 'RMDocument' interface and that I could name it what-ever I want. I wanted to prefix the name with 'm' in order to distinguish it from other variable names (Kind of like member variables in C++). Apparently that was a big no no.
So why is the variable name had such a big effect?
I have placed the entire source for the project at:
https://www.dropbox.com/sh/fq166ap3xzlw5xc/EZJXqIZPRY/RaiseMan
Thanks!
The name of your accessor method needs to follow the naming conventions: for a property "foo", the setter is "setFoo" (note the capitalization). So, you need to have setMEmployees, not setmEmployees.
As a side note, your idea of prefixing member variables with "m" is not typical Cocoa style; it may make your code more difficult for others to read.

Understanding and Reproducing the KVC Hillegass Way to Insert/Remove Objects in/from Controllers

In Aaron Hillegass' Cocoa Programming for Mac OS X, the Raiseman application connects a button in Interface Builder (IB) to an NSArrayController with sent action -remove:. In the MyDocument class he implements two KVC methods:
- (void)insertObject:(Person *)p inEmployeesAtIndex:(int)index;
- (void)removeObjectFromEmployeesAtIndex:(int)index;
When this button is pressed, the -removeObjectFromEmployeesAtIndex: method is called and the currently selected Person (Model) object is removed from the array.
How does the remove: method used in IB cause the -removeObjectFromEmployeesAtIndex: method to be called?
How do I reproduce this effect with an NSTreeController?
If you want a simple built-in option, then it's only going to create an instance of the class you specified in IB. To create another instance, you're going to need to code it yourself. You should have all the information you need from the Tree Controller to insert the new class into the proper place in the hierarchy. Some diligent searching should give you the code you need.
To attempt to help you understand how the NSArrayController mechanism works, I'll explain the best I can from my knowledge of Objective-C and the runtime. Objective-C is a very dynamic language, and you can dynamically call selectors (methods). Since the NSArrayController knows the name of your class (e.g. "Employee"), its internal implementation probably looks something like the following (or easily could):
NSString *removeSelectorName = [NSString stringWithFormat:#"removeObjectFrom%#sAtIndex:",
self.objectClassName];
SEL removeSelector = NSSelectorFromString(removeSelectorName);
[dataRepresentation performSelector:removeSelector
withObject:[NSNumber numberWithInt:self.selectionIndex];
There are examples of this elsewhere in KVO, as with the +keyPathsForValuesAffecting<Key> method (documentation here), which describes which keys cause another key to be updated. If your key is named fullName and it updates whenever the first or last name changes, you would implement this in your class:
+ (NSSet *)keyPathsForValuesAffectingFullName {
return [NSSet setWithObjects:
#"firstName",
#"lastName",
nil];
}
Further searching (and this question) turned up this documentation page, which explains the semantics of how that method gets called.

Coercing a KVC type

I would like to parse XML to populate KVC compliant objects but, my parser is very dumb, it simply assembles NSStrings from the XML attributes/tags and tries to set them via KVC.
This works for actual strings and numbers (I believe) but I need to also set dates. The problem is obviously that the parser doesn't know the string represents a date and it tries to sit it using the vanilla KVC calls - afterwhich the KVC framework complains about the type mismatch (setting a string on a date field).
Is there a programmatic way to 'intercept' invocations into the KVC framework such that I can alter the data being set (run a date string through an NSDateFormatter)?
I could put some intelligence into the parser but before doing so, are there any other well-known solutions for this type of problem?
This might not be the perfect solution, but... I'd like to share my ideas ;)
So, first of all, take a look here: Key-Value Coding - Validation. That document describes a neat way to validate your variable the moment it's set via KVC. You could use this to your advantage by:
First implement KV Validation method for your class variable
Set your value
In your validation method check if it's a date/string/whatever you wish - and change it to proper type.
This should provide a clean implementation for ensuring proper type.
Cheers,
Pawel
With KVC, everything goes through a default implementation of setValue:forKey: whichs calls the appropriate mutator method (as described here).
You can just override setValue:forKey: to check for the key or keys that need transforming, and make appropriate changes.
- (void)setValue:(id)value forKey:(NSString *)key
{
if([key isEqualToString:#"someDate"]) {
NSDateFormatter *dateFormatter = [[[NSDateFormatter alloc] init] autorelease];
someDate = [dateFormatter dateFromString:value];
value = somedate;
}
[super setValue:value forKey:key];
}
That's from memory, so no guarantees whether it'll actually compile and run. ;-)

Resources