Search code examples
objective-cnsdictionarykey-value-observingfoundation

How can I optimize this huge if/else if block within observeValueForKey


I have a controller that is registered as an observer for a LOT of properties on views. This is our -observeValueForKeyPath:::: method:

-(void)observeValueForKeyPath:(NSString *)keyPath
                     ofObject:(id)object
                       change:(NSDictionary *)change
                      context:(void*)context
{

   if( context == kStrokeColorWellChangedContext )
   {
      [self setValue:[change objectForKey:NSKeyValueChangeNewKey] forKey:kStrokeColorProperty];
   }
   else if( context == kFillColorWellChangedContext )
   {
      [self setValue:[change objectForKey:NSKeyValueChangeNewKey] forKey:kFillColorProperty];
   }
   else if( context == kBodyStyleNumChangedContext )
   {
      [self setValue:[change objectForKey:NSKeyValueChangeNewKey] forKey:kBodyStyleNumProperty];
   }
   else if( context == kStyleChangedContext )
   {
      [self setValue:[change objectForKey:NSKeyValueChangeNewKey] forKey:kStyleProperty];
   }
   else if( context == kStepStyleChangedContext )
   {
      [self setValue:[change objectForKey:NSKeyValueChangeNewKey] forKey:kStepStyleProperty];
   }
   else if( context == kFirstHeadStyleChangedContext )
   {
      [self setValue:[change objectForKey:NSKeyValueChangeNewKey] forKey:kFirstHeadStyleProperty];
   }
   else if( context == kSecondHeadStyleChangedContext )
   {
      [self setValue:[change objectForKey:NSKeyValueChangeNewKey] forKey:kSecondHeadStyleProperty];
   }

And there's actually about 3x more of these else if statements.
One thing you can see is that each block has the same code, which makes me think that it's possible to optimize this.

My initial thought was to have an NSDictionary called keyPathForContextDictionary where the keys are the constants with the Context suffix (of type void*), and the values are the appropriate string constants, denoted by the Property suffix

Then this method would only need one line:

[self setValue:[change objectForKey:NSKeyValueChangeNewKey] forKey:keyPathForContextDictionary[context]];

Note that I need to use a data structure of some sort to identify which keyPath to use, and I can't simply use the keyPath argument passed into the method. This is because there are multiple views that have the same property I'm observing (for example, color wells have the color property). So each view needs to determine a unique keypath, which is currently being determined based off of the context

The problem with this is that you cannot use void* as keys in an NSDictionary. So... does anybody have any recommendations for what I could do here?

EDIT: Here's an example of how the constants are defined:

void * const kStrokeColorWellChangedContext = (void*)&kStrokeColorWellChangedContext;
void * const kFillColorWellChangedContext = (void*)&kFillColorWellChangedContext;
void * const kBodyStyleNumChangedContext = (void*)&kBodyStyleNumChangedContext;
void * const kStyleChangedContext = (void*)&kStyleChangedContext;

NSString *const kStrokeColorProperty     = @"strokeColor";
NSString *const kFillColorProperty       = @"fillColor";
NSString *const kShadowProperty          = @"shadow";
NSString *const kBodyStyleNumProperty    = @"bodyStyleNum";
NSString *const kStyleProperty           = @"style";

Solution

  • Josh Caswell had a great answer, but I didn't want to modify the type of our constants into NSStrings*

    So a solution instead, was to cast the void* into NSValues w/ -valueWithPointer. This way I could use the void* as keys in my dictionary

    Here's the code:

       NSString *toolKeyPath = [[ToolController keyPathFromContextDictionary] objectForKey:[NSValue valueWithPointer:context]];
    
       if( toolKeyPath )
       {
          if( [change objectForKey:NSKeyValueChangeNewKey] == (id)[NSNull null] )
          {
             [self setValue:nil forKey:toolKeyPath];
          }
          else
          {
             [self setValue:[change objectForKey:NSKeyValueChangeNewKey] forKey:toolKeyPath];
          }
       }
    

    And the dictionary:

    +(NSDictionary*) keyPathFromContextDictionary
    {
       return @{
                 [NSValue valueWithPointer:kStrokeColorWellChangedContext] : kStrokeColorProperty,
                 [NSValue valueWithPointer:kFillColorWellChangedContext] : kFillColorProperty,
                 [NSValue valueWithPointer:kBodyStyleNumChangedContext] : kBodyStyleNumProperty,
                 [NSValue valueWithPointer:kStyleChangedContext] : kStyleProperty,
                 [NSValue valueWithPointer:kStepStyleChangedContext] : kStepStyleProperty,
                 [NSValue valueWithPointer:kFirstHeadStyleChangedContext] : kFirstHeadStyleProperty,
                 [NSValue valueWithPointer:kSecondHeadStyleChangedContext] : kSecondHeadStyleProperty,
                 [NSValue valueWithPointer:kShadowChangedContext] : kShadowProperty,
                 [NSValue valueWithPointer:kStrokeWidthChangedContext] : kStrokeWidthProperty,
                 [NSValue valueWithPointer:kBlurRadiusChangedContext] : kBlurRadiusProperty,
                 [NSValue valueWithPointer:kFontSizeChangedContext] : kFontSizeProperty
             };
    }