5

I realize that there is some subjectivity in the question, but considering that Apple development is pretty opinionated about naming conventions I want to do this in the way that others will understand what my coding is doing. I am trying to ask the question in the most generic way, But I'll add some of my specific details in the comments in case it affects your answer.

Let's say that I am supporting both iOS 6 and iOS 7. There is a new method on an existing class that only exists in the iOS 7 SDK. Assume that implementing the functionality in a way that is "good enough" for my app is fairly straightforward. But, of course, I'd rather use the SDK version as it is likely to be better supported, more efficient, and better handle edge cases.

As documented in this Q&A it is straightforward to handle this situation.

if ([myInstance respondsToSelector:@selector(newSelector)]) {
    //Use the SDK method
} else {
    //Use my "good enough" implementation.
}

But I don't want to litter my code with a whole bunch of conditional invocations. It seems that it would be better to encapsulate this dynamic method selection. (Especially in my case, where the method hasn't actually shipped yet and the name/signature might change.)

My instinct is to add a class category that implements both my functionality as well as a wrapper method that implements this dynamic selection of method.

Is this the right approach? If so, what naming conventions should I use? (I obviously can't name my method the same as the iOS7 method or there would be naming collisions.)

My gut reaction is to call my wrapper method safeNewSelector and my implementation a private method called lwNewSelector (where lw is my standard class prefix). But I'd much rather use something that would be considered a standard naming convention.

Community
  • 1
  • 1
David Ogren
  • 4,396
  • 1
  • 19
  • 29
  • My specific situation is a little more odd than the generic version I posted above. The method I am interested in currently only exists in Mavericks. (For strict NDA compliance reasons I don't want to mention which method, but it is a very standard and boring foundation class.) But at WWDC the implication was that the method would be coming to iOS as well. (I'm not sure if that means a later iOS 7 beta or iOS 8.) But I don't think this affects the question, since I'm testing for the method and not for a specific SDK. – David Ogren Jul 31 '13 at 13:44
  • 1
    Your approach should be fine. As for the naming conventions, is anybody actually going to see the code? If not, you don't need to worry. It'll all be compiled anyway. If yes, unfortunately I can't help you. – Macro206 Jul 31 '13 at 18:51
  • 1
    Thanks for the feedback, it's good to know that approach sounds reasonable to someone else. I always assume that someone else will read my code. Yes, this is a solo project today. But maybe I bring on a contractor someday. Or sell the project. Or just want to use this a piece of sample code to a potential employer. Not to mention that someday I'll look back on this code myself, long after I've forgotten my original intent. Thanks again for responding. – David Ogren Aug 01 '13 at 19:03
  • 2
    If you want to get fancy, you could put some code in the `+[class initialize]` method to check if the method in question already exists. If it does, presumably you are running against the new SDK and you are done. If not, you can use the objective-C runtime to add your own implementation of the method (using `class_addMethod()`). Then the rest of your code can be written to not care whether it is using the SDK version or your own implementation. – Alex MDC Aug 07 '13 at 13:41

3 Answers3

3

My instinct is to add a class category that implements both my functionality as well as a wrapper method that implements this dynamic selection of method.

That sounds right. The naming convention for category methods is a lowercase prefix, plus underscore. So, if you are shadowing a method called doSomething:withAwesome:, you would name your category method ogr_doSomething:withAwesome: (assuming you use OGR as your common prefix).

You really must prefix category methods. If two categories implement the same method, it is undefined behavior which will be run. You will not get a compile-time or runtime error. You'll just get undefined behavior. (And Apple can, and does, implement "core" functionality in categories, and you cannot easily detect that they've done so.)

Rob Napier
  • 286,113
  • 34
  • 456
  • 610
2

Go for a category and chose a name that is pretty unique, for example prefixed by some company/project specific prefix. Let's say the method in iOS 7 is going to be called funky and you chose the prefix foo. Then you'd do:

@implementation SomeClass(FooCategory)

- (void)foo_funky
{
   if ([self respondsToSelector:@selector(funky)]) {
      [self funky];
   } else {
      // Implementation of workaround.
   }
}

@end

Now, every time you'd call foo_funky that decision needs to be made. Pretty inefficient. It just occurred to me that Objective-C can make that more efficient by messing with the runtime, kind of like method-swizzling (following code is untested):

@implementation SomeClass(FooCategory)

- (void)foo_funky
{
    // Empty implementation, it will be replaced.
}

- (void)foo_myFunkyImplementation
{
    // Workaround implementation in case the iOS 7 version is missing.
}

+ (void)load
{
    Method realMethod, dummyMethod;

    realMethod = class_getInstanceMethod(self, @selector(funky));
    if (!realMethod) {
        // iOS7 method not available, use my version.
        realMethod = class_getInstanceMethod(self, @selector(foo_myFunkyImplementation));
    }

    // Get the method that should be replaced.
    dummyMethod = class_getInstanceMethod(self, @selector(foo_funky));

    // Overwrite the dummy implementation with the real implementation.
    method_setImplementation(dummyMethod, method_getImplementation(realMethod));
}

@end

This way every time you call foo_funky the correct method is called without the overhead of responds-to-selector-and-then-call-other-method.

You could also use the runtime class modifications to add your implementation using the official name when it's not available, but I don't recommend that. It's better when you can tell by the method name that it might not be the version you're expecting.

DarkDust
  • 90,870
  • 19
  • 190
  • 224
0

It is a fair question indeed and I think many Objective-C debs have run into this situation.

I have used the approach that you suggest, using a class category, in several places myself. As for the naming, in most cases I put a little extra functionality into my category method, so my method names most of the time take another argument – in most cases a simple animated:(BOOL)animated added to the end of the "official" method name.

Yes, there's a risk of clashing with future SDK releases, but I wouldn't worry too much about it, Xcode's refactoring works reasonably well and you'll get a linker warning when category methods conflict.

Edit: As Rob points out, using that naming convention is probably a good idea.

Pascal
  • 16,846
  • 4
  • 60
  • 69
  • You should worry a great deal about it. Category name collisions is undefined behavior. You will not get an error. Either implementation may run. I've encountered this bug myself when colliding with one of Apple's private categories. – Rob Napier Aug 07 '13 at 15:54
  • I just checked, you do get a linker warning saying `instance method 'doStuff' in category from ... conflicts with same method from another category`, at least in Xcode 5. Anyway, your prefix hint is a good idea. – Pascal Aug 08 '13 at 19:52
  • Note that this warning only works at compile time if the compiler can see both categories (i.e. usually because it has header files that define both). It doesn't protect you against future SDKs (since you don't have the future SDK's header file) and it doesn't protect you against private categories. – Rob Napier Aug 08 '13 at 21:10
  • Indeed, the linker doesn't warn when overriding e.g. `NSStringDrawing` category methods. Good call! – Pascal Aug 08 '13 at 22:17