Is overriding Objective-C framework methods ever a good idea?

ObjC has a very unique way to override methods. In particular, you can override functions in your own OSX framework. Through "categories" or "Swizzling." You can even override "hidden" functions that are used only internally.

Can someone provide me an example where there is a good reason for this? Something that you would like to use in the released commercial software, and not just some hacked tool for internal use?

For example, perhaps you wanted to improve some inline method, or perhaps there was a bug in the framework method that you wanted to fix.

You can also explain why this is best done with functions in ObjC and not in C ++ / Java and the like. I mean, I heard about the possibility of loading the C library, but letting me replace some functions with functions with the same name that were loaded earlier. How is ObjC better for changing library behavior than this?

+4
source share
3 answers

If you expand the question from simple swizzling to actually modifying the library, I can come up with useful examples.

As in iOS 5, NSURLConnection provides sendAsynchronousRequest:queue:completionHandler: which is a lock (/ close) method for performing asynchronous loading from any resource identified by a URL (local or remote). This is a very useful way, as it makes your code cleaner and smaller than the classic delegate, and it is much more likely that the related parts of your code will be close to each other.

This method is not provided in iOS 4. So I did in my project that when I start the application (via the appropriate + (void)load ) I check if the method is defined. If I do not fix its implementation on the class. From now on, every other part of the program can be written into the iOS 5 specification without any verification of version or accessibility, just as if I focused only on iOS 5, except that it will also work on iOS 4.

In Java or C ++, I assume that the same kind of thing will be achieved by creating your own class to send URL connections that perform runtime checks on every call. This is the worst decision, because it is difficult for him to retreat. So, if I decided one day to support iOS 5, I just delete the source file, which adds my implementation of sendAsynchronousRequest:... Nothing changed.

As for the swizzling method, the only thing I see in it is that someone wants to change the functionality of an existing class and does not have access to the code in which the class is created. Thus, you usually talk about trying to change a logically opaque code from the outside, making assumptions about its implementation. I would not support this as an idea in any language. I assume it is more recommended in Objective-C, because Apple is more inclined to make things opaque (see, for example, every application that wants to show a customized camera view prior to iOS 3.1, every application that wants to execute a custom processing on camera input earlier to iOS 4.0, etc.), but not because it is a good idea in Objective-C. This is not true.

EDIT: so in the rest of the presentation, I cannot publish the full code because I wrote it as part of my work, but I have a class called NSURLConnectionAsyncForiOS4 with an implementation of sendAsynchronousRequest:queue:completionHandler: This implementation is actually quite trivial, just sending the operation to the designated queue, which performs the synchronous load through the old sendSynchronousRequest:... interface, and then sends the results from this to the handler.

This class has + (void)load , which is the method of the class that you add to the class, which will be released immediately after this 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 through my C interface to check if sendAsynchronousRequest:... is NSURLConnection on NSURLConnection . If this is not the case, I add my implementation to NSURLConnection , so it is now defined. This is clearly not swizzling - I am not adjusting the existing implementation of anything, I just add a custom implementation of something if Apple is not available. Corresponding runtime calls are objc_getClass , class_getClassMethod and class_addMethod .

In the rest of the code, when I want to make an asynchronous URL connection, I just write, for example.

 [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 in the iOS 5 API and doesn’t know or care that I have a gasket somewhere else to ensure that one microscopic part of iOS 5 changes to iOS 4. And, as I already said when I stop supporting iOS 4, I just remove the gasket from the project and the rest of the code will continue to not know and not care.

I had similar code to provide an alternative partial implementation of NSJSONSerialization (which dynamically created a new class at runtime and copied it); one tweak you need to make is that links to NSJSONSerialization elsewhere will resolve once during linker loading, which you really don't want. So I added a quick #define from NSJSONSerialization to NSClassFromString(@"NSJSONSerialization") in my precompiled header. Which is less functionally neat, but a similar course of action in terms of finding a way to support iOS 4 support so far is simply writing the rest of the project to iOS 5 standards.

+2
source

There are both good and bad cases. Since you did not mention anything, in particular, these examples will be all-in-place.

It is perfectly fine ( good idea ) to override framework methods when subclassing:

  • When subclassing NSView (from AppKit.framework), it is expected that you override drawRect:(NSRect) . This is the mechanism used to draw views.
  • When creating a custom NSMenu, you can override insertItemWithTitle:action:keyEquivalent:atIndex: and any other methods ...

The main thing when a subclassification is whether your behavior completes the redefinition of the old behavior ... or extends it (in this case, your redefinition ultimately calls [super ...]; )


However, you should always avoid using (and overriding) any private API methods (usually they have an underscore prefix in their name). This is a bad idea .

You also should not redefine existing methods through categories. This is also bad . It has undefined behavior.

+1
source

If you are talking about categories , you do not override the methods with them (because there is no way to call the original method, for example, calling super with a subclass), but only completely replace it with your own, which makes the whole idea basically meaningless. Categories are useful only for the safe expansion of functionality, and that the only use I have even seen (and this is a very good, great idea), although they can really be used for dangerous things.

If you mean overriding a subclass , this is not unique. But in Obj-C, you can override everything, even private, undocumented methods, and not just what was declared "overridable", as in other languages. Personally, I find this enjoyable, as I remember that in Delphi and C ++ I used to β€œcrack” access to private and protected members to bypass the internal error within the framework. This is not a good idea, but at some points it can be a life saver.

There is also a swizzling method, but this is not a standard language feature that hack. Hacking undocumented internal components is rarely a good idea.

And in relation to β€œ how can you explain why this can be done best with functions 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). If this is not artificially forbidden, then in every dynamic language this takes place.

See the dynamic language wikipedia page for a more detailed explanation and other examples. For example, in Obj-C and other dynamic languages, it is perhaps even more wonderful that an object can change its type (class) in place without rest.

+1
source

Source: https://habr.com/ru/post/1403138/


All Articles