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];
}
Related
I need to be able to compare two versions of a plist file created with NSKeyedArchiver. In particular, it's the "elements" file created in Xcode for a .xcdatamodeld file.
Since I have not created this file myself I can not recreate its object model. Instead, I need to understand which classes and keyed properties the archive contains.
Ideally, I want to create a tree containing strings representing the names of classes along with their property names and values. I assume that all this information is stored in the archive, so it should be possible to generically parse this archive, right?
I've read about NSKeyedUnarchiver and its delegate. I've only gotten as as as this:
Unarchive the file data:
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
unarchiver.delegate = self;
id graph = [unarchiver decodeObjectForKey:#"root"];
Here's the delegate method that gets called when an unknown class is requested. I return a proxy class here in which I hope to collect its values:
- (Class)unarchiver:(NSKeyedUnarchiver *)unarchiver cannotDecodeObjectOfClassName:(NSString *)name originalClasses:(NSArray *)classNames
{
NSLog(#"wants: %#", name);
return [ObjProxy class];
}
The proxy class implements
- (id)initWithCoder:(NSCoder *)aDecoder
in which I do not know how to proceed, not knowing the actual properties of the classes. NSCoder doesn't seem to provide any function to learn of the available keys. Is there a trick to get to them, maybe by overriding some of the lower level objc methods?
So far, with this little code shown above, when parsing the "elements" file, I only get the request for one class, "XDPMModel", and then it's done.
Any ideas how to get this working, i.e. traverse the tree deeper?
You can use PlistExplorer, it's a Cocoa Tool to inspect files written by NSKeyedArchiver.
I've done exactly this before, to decode objects stored in the preferences file for Panic's Coda. What I did was to use class-dump on the app (in your case Xcode and its related frameworks), which allows you to see the properties of the real objects, and use these to create proxy objects that match those properties. You can then use NSKeyedUnarchiver successfully and query the proxy objects for their values.
It's a fairly laborious process but it works perfectly. In my case I was working with a user defaults plist so I only had to define a couple of these proxy classes for the objects I was interested in, but I would imagine that you'll have to define quite a few for Xcode.
I'd also be very interested to know if there's a way to do this without having to go the class-dump route (possibly, as you say, via some of the lower-level Objective-C functions) because it would greatly simplify this type of reverse engineering.
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. ;-)
What do I need to do to update a tableView bound to an NSArrayController when a method is called that updates the underlying array? An example might clarify this.
When my application launches, it creates a SubwayTrain. When SubwayTrain is initialised, it creates a single SubwayCar. SubwayCar has a mutable array 'passengers'. When a Subway car is initialised, the passengers array is created, and a couple of People objects are put in (let's say a person with name "ticket collector" and another, named "homeless guy"). These guys are always on the SubwayCar so I create them at initialisation and add them to the passengers array.
During the life of the application people board the car. 'addPassenger' is called on the SubwayCar, with the person passed in as an argument.
I have an NSArrayController bound to subwayTrain.subwayCar.passengers, and at launch my ticket collector and homeless guy show up fine. But when I use [subwayCar addPassenger:], the tableView doesn't update. I have confirmed that the passenger is definitely added to the array, but nothing gets updated in the gui.
What am I likely to be doing wrong? My instinct is that it's KVO related - the array controller doesn't know to update when addPassenger is called (even though addPassenger calls [passengers addObject:]. What could I be getting wrong here - I can post code if it helps.
Thanks to anyone willing to help out.
UPDATE
So, it turns out I can get this to work by changing by addPassenger method from
[seatedPlayers addObject:person];
to
NSMutableSet *newSeatedPlayers = [NSMutableSet setWithSet:seatedPlayers];
[newSeatedPlayers addObject:sp];
[seatedPlayers release];
[self setSeatedPlayers:newSeatedPlayers];
I guess this is because I am using [self setSeatedPlayers]. Is this the right way to do it? It seems awfully cumbersome to copy the array, release the old one, and update the copy (as opposed to just adding to the existing array).
I don't know if its considered a bug, but addObject: (and removeObject:atIndex:) don't generate KVO notifications, which is why the array controller/table view isn't getting updated. To be KVO-compliant, use mutableArrayValueForKey:
Example:
[[self mutableArrayValueForKey:#"seatedPlayers"] addObject:person];
You'll also want to implement insertObject:inSeatedPlayersAtIndex: since the default KVO methods are really slow (they create a whole new array, add the object to that array, and set the original array to the new array -- very inefficient)
- (void)insertObject:(id)object inSeatedPlayerAtIndex:(int)index
{
[seatedPlayers insertObject:object atIndex:index];
}
Note that this method will also be called when the array controller adds objects, so its also a nice hook for thinks like registering an undo operation, etc.
I haven't tried this, so I cannot say it works, but wouldn't you get KVO notifications by calling
insertObject:atArrangedObjectIndex:
on the ArrayController?
So, it turns out I can get this to work by changing by addPassenger method from
[seatedPlayers addObject:person];
to
NSMutableSet *newSeatedPlayers = [NSMutableSet setWithSet:seatedPlayers];
[newSeatedPlayers addObject:sp];
[seatedPlayers release];
[self setSeatedPlayers:newSeatedPlayers];
I guess this is because I am using [self setSeatedPlayers]. Is this the right way to do it?
First off, it's setSeatedPlayers:, with the colon. That's vitally important in Objective-C.
Using your own setters is the correct way to do it, but you're using the incorrect correct way. It works, but you're still writing more code than you need to.
What you should do is implement set accessors, such as addSeatedPlayersObject:. Then, send yourself that message. This makes adding people a short one-liner:
[self addSeatedPlayersObject:person];
And as long as you follow the KVC-compliant accessor formats, you will get KVO notifications for free, just as you do with setSeatedPlayers:.
The advantages of this over setSeatedPlayers: are:
Your code to mutate the set will be shorter.
Because it's shorter, it will be cleaner.
Using specific set-mutation accessors provides the possibility of specific set-mutation KVO notifications, instead of general the-whole-dang-set-changed notifications.
I also prefer this solution over mutableSetValueForKey:, both for brevity and because it's so easy to misspell the key in that string literal. (Uli Kusterer has a macro to cause a warning when that happens, which is useful when you really do need to talk to KVC or KVO itself.)
The key to the magic of Key Value Observing is in Key Value Compliance. You initially were using a method name addObject: which is only associated with the "unordered accessor pattern" and your property was an indexed property (NSMutableArray). When you changed your property to an unordered property (NSMutableSet) it worked. Consider NSArray or NSMutableArray to be indexed properties and NSSet or NSMutableSet to be unordered properties. You really have to read this section carefully to know what is required to make the magic happen... Key-Value-Compliance. There are some 'Required' methods for the different categories even if you don't plan to use them.
Use willChangeValueForKey: and didChangeValueForKey: wrapped around a change of a member when the change does not appear to cause a KVO notification. This comes in handy when you are directly changing an instance variable.
Use willChangeValueForKey:withSetMutation:usingObjects: and didChangeValueForKey:withSetMutation:usingObjects: wrapped around a change of contents of a collection when the change does not appear to cause a KVO notification.
Use [seatedPlayers setByAddingObject:sp] to make things shorter and to avoid needlessly allocating mutable set.
Overall, I'd do either this:
[self willChangeValueForKey:#"seatedPlayers"
withSetMutation:NSKeyValueUnionSetMutation
usingObjects:sp];
[seatedPlayers addObject:sp];
[self didChangeValueForKey:#"seatedPlayers"
withSetMutation:NSKeyValueUnionSetMutation
usingObjects:sp];
or this:
[self setSeatedPlayers:[seatedPlayers setByAddingObject:sp]];
with the latter alternative causing an automatic invocation of the functions listed under 1. First alternative should be better performing.
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.
In my app, I have a NSDictionary whose keys should be instances of a subclass of NSManagedObject.
The problem, however, is that NSManagedObject does not implement the NSCopying protocol which means that no Core Data objects / instances of NSManagedObject can be used as dictionary keys even though the -[hash] method works fine for them.
Was should I do?
There are four options:
Use a different object as the dictionary key instead, and lookup from that. [object objectID] or +[NSValue valueWithNonretainedObject:] seem the most obvious
Use CFDictionaryCreateMutable() to create a dictionary with retained keys, rather than copied, instead, and then call CFDictionarySetValue() to store the objects
On OS X or iOS6+, [NSMapTable mapTableWithStrongToStrongObjects] gives you a purely Objective-C equivalent to CFMutableDictionary
Implement NSCopying for your managed object subclass, such that it returns self (with a bumped reference count if you're not using ARC)
Notes
+valueWithNonretainedObject: is pretty dangerous, since it's possible to be left with a dangling pointer; likely best to avoid.
Storing object IDs is fine, apart from the fact that new objects start out life with a temporary ID. That ID then changes to a permanent one when the context is saved to disk (or -obtainPermanentIDsForObjects:… is called). Your mapping code needs to be smart enough to handle this unless it can guarantee that all incoming objects already have a permanent ID.
Implementing NSCopying like this feels a bit icky, but should work just fine. As it happens, this is exactly the approach NSURLSessionTask takes, I presume for dictionary friendliness.
Prior to OS X 10.8 Mountain Lion, it used to be possible to create a regular NSMutableDictionary and then call CFDictionarySetValue() for it. That's no longer the case though; new dictionaries now have proper copy callbacks specified down at the CF level, rather than purely being a feature of NSMutableDictionary.
I suggest to use [[[myManagedObject objectID] URIRepresentation] absoluteString] as your key.
Could you create a wrapper class, that contains a reference to the instance of NSManagedObject that you want to use as a dictionary key? You could then make this wrapper class implement NSCopying, along with a hash method (perhaps just calling the NSManagedObject's hash method), and use this wrapper as the dictionary key.
I had a similar problem, in which I needed to bundle several entities with additional data for each, and initially tried:
#{entity1:data1, #entity2:data2, #entity3:data3}
this didn't work for the reason above (NSCopying), so I did:
#[
#{#"entity":entity1, #"data":data1},
#{#"entity":entity2, #"data":data2},
#{#"entity":entity3, #"data":data3}
]
But this solution makes sense only if you don't need dictionary style access to these entities or are happy to iterate to find what you need. In my case this was a packaging problem. Note that if you pass these entities around the NSManagedObjectContext need to be the same to use them.