问题
I'm trying to catch/override certain methods that get called within objects I have no access to. I would like to be able to subclass objects that are instantiated within system objects.
For example, when I instantiate a UIWebView, it internally instantiates a UIScrollView. I'd like to be able to subclass that UIScrollView so I can modify certain aspects of its behavior by subclassing it. Is that possible?
I have been trying to do this by using a category, @implementation UIScrollView (MyScrollView), then overriding + alloc, and returning my own object which is a subclass of UIScrollView. However, I'm running into some problems. Basically, I can't seem to reach the original [UIScrollView alloc] method. Perhaps I could implement my own + alloc method that would replicate the [NSObject alloc] behavior?
Is there an easier way to do this? What is wrong with my current approach?
Finally, would doing this pass Apple's review process? I am not calling anything that is undocumented.
回答1:
The problem of categories is, they are not allowing proper overriding as you will not be able to reach the original methods anymore.
To achieve such (forgive my french) dirty tricks, you could try MethodSwizzling. As a last resort, this sometimes is of great value. I have used it in the past for debugging mainly and also for reverse engineering.
For your question on the Apple approval process, I would guess that your chances of getting into trouble are pretty high when changing the default implementation of a UIWebView in such radical way.
回答2:
Don't use a category to clobber existing methods. That way lies madness.
What I'd do in your situation is replace the scrollview with an instance of your custom scrollview subclass by manipulating the view hierarchy, not the allocation mechanism.
回答3:
@Till's approach of Method Swizzling is how you achieve this. Mike Ash has one of the better write-ups of how to do it. I currently favor his Direct Override approach, though I've reworked it a little as follows:
#import <objc/runtime.h>
@interface NSObject (RNSwizzle)
+ (IMP)swizzleSelector:(SEL)origSelector withIMP:(IMP)newIMP;
@end
@implementation NSObject (RNSwizzle)
+ (IMP)swizzleSelector:(SEL)origSelector withIMP:(IMP)newIMP {
Class class = [self class];
Method origMethod = class_getInstanceMethod(class, origSelector);
IMP origIMP = method_getImplementation(origMethod);
if(!class_addMethod(self, origSelector, newIMP,
method_getTypeEncoding(origMethod)))
{
method_setImplementation(origMethod, newIMP);
}
return origIMP;
}
@end
Given his example, you would use my method this way:
gOrigDrawRect = [UIView swizzleSelector:@selector(drawRect:)
withIMP:OverrideDrawRect];
These are all documented calls and do not make use of Apple private APIs. Apple does sometimes reject apps that make too dramatic a change to expected UI behaviors (regardless of how they do it), but I've shipped stuff with pretty dramatic under-the-covers UI modifications without trouble, and this does not rely on private APIs.
That doesn't mean that Method Swizzling is stable. It's a fragile technique and does make you more reliant on undocumented internals. It may mean you have to scramble more if Apple changes something.
(Deep breath)
There's another approach you can use here, as long as you require no ivar storage. You can grab the scroll view and class swizzle it to your own subclass. Did I mention you must have no ivar storage of your own? The bugs you'll create if you allocate an ivar in this and mind-bending. Deusty writes this up well in his blog. (It was his code that I added an ivar to and encountered the incredibly mind-bending bugs mentioned above.)
Again, this is a fragile, crazy, dangerous technique. It also can sometimes be the most elegant and maintainable way to do what you need.
回答4:
I have not actually done it, so I am not 100% certain, but I believe you can do what your are trying to do, but I do not think you can do so with your current approach. I believe you will have to use the Objective-C runtime to intercept methods. If you haven't already, read The Objective-C Runtime Reference.
OK, that's the can now what about the should you and will you get away with it?
Anything you accomplish by doing this will probably be very fragile. Any future changes Apple make to the frameworks may break your intercept code. So personally, I would file this under "very dubious" as to whether you should consider releasing a product that relies on this.
As for will you get away with it? Obviously, I can't speak for Apple, but my bet is no. You are correct that strictly speaking your are not calling undocumented code, but you are interfering with the functioning of private methods, so you are certainly flying in the face of the spirit of the App Store terms.
Of course, all this is merely my opinion, take from it what you will.
回答5:
I've found a relatively easy, fairly safe solution. It's not perfect and won't work in all cases, but might be useful in some cases.
Basically, what I'm doing is using a category to catch my desired class's + alloc calls. Then I implement my own + alloc method, which simply allocates and returns an object of my subclassed type. I can then override any method in my subclassed object and they get called correctly from UIWebView. This is an automated subclassing mechanism: Every instance of the target class (in this case, UIScrollView) gets automatically subclassed, even if someone else creates it.
Here's what my + alloc method looks like:
+ (id) alloc
{
return class_createInstance([MyUIScrollView class], 0);
}
I then implement a simple MyUIScrollView class that inherits from UIScrollView and lets me override any methods I may need, which gives me much more control over the behavior of the the parent UIWebView. Cool!
A couple of catches:
If your application has more than one UIScrollViews in it, you need to be careful because every single one of them would now essentially become MyUIScrollView! The solution is to have a flag within MyUIScrollView that ensures that it's dormant (basically just forwards everything to the superclass) in cases where the instance is not owned by your specific UIWebView. This is easily done by subclassing the UIWebView, and overriding its view hierarchy methods (addSubview:, etc.) so that once UIWebView instantiates and adds its internal UIScrollView to the view hierarchy, you'll be able to set a flag in your subclassed instance to tell it to do whatever you're looking to do.
This only works for applications that do not subclass the target class (UIScrollView, etc.) anywhere in the application! For example, I am successfully able to squeeze myself into the UIWebView's UIScrollView using this technique and override whatever methods I'd like to within it, but if some other component in my application subclasses UIScrollView (including my own code), I will run into trouble because I am essentially disabling all subclassing for this class. All UIScrollViews in the application are going to be of type MyUIScrollView!
Bottom line: This is not a recommended approach, but it looks like the cleanest way to address this issue.
来源:https://stackoverflow.com/questions/6035021/trapping-objective-c-classes