Search code examples
iosobjective-ccocoafoundationobjective-c-category

NSString Subclass or Wrapper Class or Category


I'm currently helping a client that needs to change the language in their app due to certain governmental guidelines (medical vs wellness wording). Their app is huge and all the strings are contained in the code i.e. (stringWithFormat/hardcoded), none of it is in an external table. Meaning this would be a huge manual task.

At a undetermined point in the future the client believes they will receive approval to return to their current wording and will want to switch the strings back. Most of the changes will literally be switching a single problematic word to a less problematic word.

I thought that maybe if I could change the strings at run time based on a bool switch that it might eliminate the manual work involved and it would let me switch the language back when needed.

First attempt:

+ (instancetype)stringWithFormat:(NSString *)format, ...
{
   va_list args;
   va_start(args,format);
   //todo check flag if we're changing the language
   //todo replace problematic word from 'format'
   NSString *result = [NSString stringWithFormat:format,args];

   return result;
}

I first quickly coded up a category to override stringWithFormat to replace problematic words. I forgot that I would lose the original implementation of stringWithFormat. This resulted in endless recursion.

Next Attempt (subclass):

I started an attempt to subclass NSString but hit a stackoverflow post saying that if my solution was to subclass a class cluster then I didn't understand my problem because subclassing a class cluster is almost never done.

Final Option (wrapper):

My final attempt would be to write a wrapper class but that kind of defeats the original purpose which was to avoid having to manually seek out each string in the app.

I'm not really sure how to approach this problem anymore. What do I do if I need to add/override functionality to one of the core classes.


Solution

  • There is nothing wrong with the idea of your first attempt, just a little mistake in its implementation. Change:

    NSString *result = [NSString stringWithFormat:format,args];
    

    to:

    NSString *result = [NSString alloc] initWithFormat:format arguments:args];
    

    which is the expansion of stringWithFormat: and the interception will work.

    Thoughts about class clusters are a red herring in this particular situation, the front class for a cluster (NSString) must provide implementations for class methods (+stringWithFormat:), so you can use a simple category to intercept them.

    However, having intercepted +stringWithFormat: be careful. A simple test will show you it is used a lot by the frameworks and you do not wish to break them - as my first simple test did by simply changing "d" to "c", which changes "window" to "wincow", which in turn broke the binding setup of Xcode's default app which binds the property "window"...

    If you are changing health-related words you might be OK, whole strings would be better.

    A better approach might be to simply write a RE to match all the literal strings in the code and replace them by function(string) for some function (not method) you write to do the conversion.

    HTH