2

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.

the Reverend
  • 12,305
  • 10
  • 66
  • 121

4 Answers4

1

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.

Cristik
  • 30,989
  • 25
  • 91
  • 127
1

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:

  1. 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

  2. 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

Community
  • 1
  • 1
CRD
  • 52,522
  • 5
  • 70
  • 86
1

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.

sergeyne
  • 1,268
  • 12
  • 18
0

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];

}
Jay
  • 34,438
  • 18
  • 52
  • 81