4

ObjC has a very unique way of overriding methods. Specifically, that you can override functions in OSX's own framework. Via "categories" or "Swizzling". You can even override "buried" functions only used internally.

Can someone provide me with an example where there was a good reason to do this? Something you would use in released commercial software and not just some hacked up tool for internal use?

For example, maybe you wanted to improve on some built in method, or maybe there was a bug in a framework method you wanted to fix.

Also, can you explain why this can best be done with features in ObjC, and not in C++ / Java and the like. I mean, I've heard of the ability to load a C library, but allow certain functions to be replaced, with functions of the same name that were previously loaded. How is ObjC better at modifying library behaviour than that?

mdb
  • 52,000
  • 11
  • 64
  • 62
Theo
  • 299
  • 2
  • 9
  • 2
    Swizzling has become a no-no and when being used will lead to rejection of an iOS app from iTunes. – Till Mar 23 '12 at 18:18
  • So there seems to be too many questions in your post :), but what you heading at is interesting. But i am sure why swizzling can't be a good choice is because of the answer to this question , http://stackoverflow.com/questions/1637604/method-swizzle-on-iphone-device – Futur Mar 23 '12 at 18:32

3 Answers3

2

If you're extending the question from mere swizzling to actual library modification then I can think of useful examples.

As of iOS 5, NSURLConnection provides sendAsynchronousRequest:queue:completionHandler:, which is a block (/closure) driven way to perform an asynchronous load from any resource identifiable with a URL (local or remote). It's a very useful way to be able to proceed as it makes your code cleaner and smaller than the classical delegate alternative and is much more likely to keep the related parts of your code close to one another.

That method isn't supplied in iOS 4. So what I've done in my project is that, when the application is launched (via a suitable + (void)load), I check whether the method is defined. If not I patch an implementation of it onto the class. Henceforth every other part of the program can be written to the iOS 5 specification without performing any sort of version or availability check exactly as if I was targeting iOS 5 only, except that it'll also run on iOS 4.

In Java or C++ I guess the same sort of thing would be achieved by creating your own class to issue URL connections that performs a runtime check each time it is called. That's a worse solution because it's more difficult to step back from. This way around if I decide one day to support iOS 5 only I simply delete the source file that adds my implementation of sendAsynchronousRequest:.... Nothing else changes.

As for method swizzling, the only times I see it suggested are where somebody wants to change the functionality of an existing class and doesn't have access to the code in which the class is created. So you're usually talking about trying to modify logically opaque code from the outside by making assumptions about its implementation. I wouldn't really support that as an idea on any language. I guess it gets recommended more in Objective-C because Apple are more prone to making things opaque (see, e.g. every app that wanted to show a customised camera view prior to iOS 3.1, every app that wanted to perform custom processing on camera input prior to iOS 4.0, etc), rather than because it's a good idea in Objective-C. It isn't.

EDIT: so, in further exposition — I can't post full code because I wrote it as part of my job, but I have a class named NSURLConnectionAsyncForiOS4 with an implementation of sendAsynchronousRequest:queue:completionHandler:. That implementation is actually quite trivial, just dispatching an operation to the nominated queue that does a synchronous load via the old sendSynchronousRequest:... interface and then posts the results from that on to the handler.

That class has a + (void)load, which is the class method you add to a class that will be issued immediately after that class has been loaded into memory, effectively as a global constructor for the metaclass and with all the usual caveats.

In my +load I use the Objective-C runtime directly via its C interface to check whether sendAsynchronousRequest:... is defined on NSURLConnection. If it isn't then I add my implementation to NSURLConnection, so from henceforth it is defined. This explicitly isn't swizzling — I'm not adjusting the existing implementation of anything, I'm just adding a user-supplied implementation of something if Apple's isn't available. Relevant runtime calls are objc_getClass, class_getClassMethod and class_addMethod.

In the rest of the code, whenever I want to perform an asynchronous URL connection I just write e.g.

[NSURLConnection sendAsynchronousRequest:request
    queue:[self anyBackgroundOperationQueue]
    completionHandler:
    ^(NSURLResponse *response, NSData *data, NSError *blockError)
    {
        if(blockError)
        {
            // oh dear; was it fatal?
        }

        if(data)
        {
            // hooray! You know, unless this was an HTTP request, in
            // which case I should check the response code, etc.
        }

        /* etc */
    }

So the rest of my code is just written to the iOS 5 API and neither knows nor cares that I have a shim somewhere else to provide that one microscopic part of the iOS 5 changes on iOS 4. And, as I say, when I stop supporting iOS 4 I'll just delete the shim from the project and all the rest of my code will continue not to know or to care.

I had similar code to supply an alternative partial implementation of NSJSONSerialization (which dynamically created a new class in the runtime and copied methods to it); the one adjustment you need to make is that references to NSJSONSerialization elsewhere will be resolved once at load time by the linker, which you don't really want. So I added a quick #define of NSJSONSerialization to NSClassFromString(@"NSJSONSerialization") in my precompiled header. Which is less functionally neat but a similar line of action in terms of finding a way to keep iOS 4 support for the time being while just writing the rest of the project to the iOS 5 standards.

Tommy
  • 99,986
  • 12
  • 185
  • 204
  • can you please explain this part of your explanation please ? "the iOS 5 specification without performing any sort of version or availability check exactly as if I was targeting iOS 5 only, except that it'll also run on iOS 4" - i would like to learn how you did this.. thank you – Futur Mar 23 '12 at 18:38
  • I've edited my post to add some more detail — is that sufficient? I'm a bit restricted in not really being able to show the code... the sum total is 63 lines (20 of them comments) so it's a tiny little thing. – Tommy Mar 23 '12 at 19:00
  • Rather too much detail for my liking but if it makes you happy I'm sure others will enjoy it too. – Theo Mar 23 '12 at 20:22
  • @Theo it's meant to make Futur happy — I appreciate it's not particularly relevant to the original question asked. – Tommy Mar 23 '12 at 20:28
  • 1
    Actually, thinking about this (from a theoretical viewpoint), if iOS's framework were written in C or C++... and we had this "library loader feature" to not load functions from a library if a function with the same name existed.... then... you could write a sendAsynchronousRequest() function, but only load it, if you couldn't load sendAsynchronousRequest() from iOS's framework. – Theo Mar 23 '12 at 23:16
  • Of course I realise that idea doesn't help anyone using iOS (your case), as that's not written in C. If it's written in ObjC then it's best replaced with a function written in ObJC. :) I'm just trying to understand the general theory and practice of replacing OS functions. – Theo Mar 23 '12 at 23:18
1

There are both good and bad cases. Since you didn't mention anything in particular these examples will be all-over-the-place.

It's perfectly normal (good idea) to override framework methods when subclassing:

  • When subclassing NSView (from the AppKit.framework), it's expected that you override drawRect:(NSRect). It's the mechanism used for drawing views.
  • When creating a custom NSMenu, you could override insertItemWithTitle:action:keyEquivalent:atIndex: and any other methods...

The main thing when subclassing is whether or not your behaviour completes re-defines the old behaviour... or extends it (in which case your override eventually calls [super ...];)


That said, however, you should always stand clear of using (and overriding) any private API methods (those normally have an underscore prefix in their name). This is a bad idea.

You also should not override existing methods via categories. That's also bad. It has undefined behaviour.

  • Yeah, I know subclassing methods like drawRect: is expected. Same with `init`, `release`, etc ;) My question was about overriding, or swizzling OSX features that weren't designed to be replaced. – Theo Mar 23 '12 at 18:57
1

If you're talking about categories, you don't override methods with them (because there is no way to call original method, like calling super when subclassing), but only completely replace with your own ones, which makes the whole idea mostly pointless. Categories are only useful for safely extending functionality, and that's the only use I have even seen (and which is a very good, an excellent idea), although indeed they can be used for dangerous things.

If you mean overriding by subclassing, that is not unique. But in Obj-C you can override everything, even private undocumented methods, not just what was declared 'overridable' like in other languages. Personally, I think it's nice, as I remember in Delphi and C++ I used to “hack” access to private and protected members to workaround an internal bug in framework. This is not a good idea, but at some moments it can be a life saver.

There is also method swizzling, but that's not standard language feature, that's a hack. Hacking undocumented internals is rarely a good idea.

And regarding “how can you explain why this can best be done with features in ObjC”, the answer is simple — Obj-C is dynamic, and this freedom is common to almost all dynamic languages (Javascript, Python, Ruby, Io, a lot more). Unless artificially disabled, every dynamic language has it.

Refer to the wikipedia page on dynamic languages for longer explanation and more examples. For example, an even more miraculous things possible in Obj-C and other dynamic languages is that an object can change it's type (class) in place, without recreation.

hamstergene
  • 24,039
  • 5
  • 57
  • 72