Switching methods in private classes in Apple frameworks

  • (following on from the thread "How to debug this error on closing a document?", but it's really moved on to a new topic at this point)

    I was not aware that poseAsClass is not available in 64 bit applications. I looked at the Apple example of exchanging a method in NSWindow, and it looked easy enough, so I tried the method exchanging by using class-dump to generate the header for NSConcreteNotification, and implemented the switch, but it gives a linker error:

    "_OBJC_CLASS_$_NSConcreteNotification", referenced from:
    l_OBJC_$_CATEGORY_NSConcreteNotification_$_MethodReplacement in MyConcreteNotification.o
    __objc_classrefs__DATA@0 in MyConcreteNotification.o
    ld: symbol(s) not found
    collect2: ld returned 1 exit status

    So it must be in the foundation binary, but the linker doesn't pick it up. Is there a way around this?

    Gideon

    On 12/03/2010, at 2:00 AM, Jerry Krinock wrote:

    >
    > On 2010 Mar 11, at 02:27, Gideon King wrote:
    >
    >> or whether I would have to subclass NSConcreteNotification and override dealloc and then use pose as, so I could  print out the notification name etc, to get the info
    >
    > That would work, but Method Replacement [1] was added in Objective-C 2.0 as a replacement for "pose as class".  However, to do Method Replacement, you need to declare and implement a category on, in this case NSConcreteNotification, but that won't compile because NSConcreteNotification is Apple-private.  Does anyone know how to do Method Replacement for debugging in an Apple-private class?
    >
    > [1] http://developer.apple.com/mac/library/samplecode/MethodReplacement/index.h
    tml

    >
  • On Mar 11, 2010, at 4:24 PM, Gideon King wrote:
    > (following on from the thread "How to debug this error on closing a document?", but it's really moved on to a new topic at this point)
    >
    > I was not aware that poseAsClass is not available in 64 bit applications. I looked at the Apple example of exchanging a method in NSWindow, and it looked easy enough, so I tried the method exchanging by using class-dump to generate the header for NSConcreteNotification, and implemented the switch, but it gives a linker error:
    >
    > "_OBJC_CLASS_$_NSConcreteNotification", referenced from:
    > l_OBJC_$_CATEGORY_NSConcreteNotification_$_MethodReplacement in MyConcreteNotification.o
    > __objc_classrefs__DATA@0 in MyConcreteNotification.o
    > ld: symbol(s) not found
    > collect2: ld returned 1 exit status
    >
    > So it must be in the foundation binary, but the linker doesn't pick it up. Is there a way around this?

    On iPhone and 64-bit Mac, the linker enforces internal classes more strictly. NSConcreteNotification is private, so you can't link to it or subclass it. You can still get the class via runtime introspection like NSClassFromString(), which for debugging purposes ought to be good enough.

    --
    Greg Parker    <gparker...>    Runtime Wrangler
  • This is really cool...so I can replace a method without being a subclass or category.

    So I implemented an NSObject subclass with the new method, and did the method exchange, and my new method was called instead of the old one, but I had a problem - the call to the switched out method (dealloc from the NSConcreteNotification) didn't work. I assumed that was because the old implementation of dealloc was now moved to my class, so I changed the method switching so that it ensures that the old dealloc is replaced with the implementation of mydealloc, and that I have added a method to the NSConcreteNotification which is called mydealloc, and does the things that the old dealloc did.

    This seems to work, so I thought I'd post it here in case anyone else needs to replace a method in a private class, and be able to call the original implementation.

    #import <objc/runtime.h>
    #import <Cocoa/Cocoa.h>

    @interface MyConcreteNotification : NSObject {
    }

    - (void)mydealloc;

    @end

    @implementation MyConcreteNotification

    + (void)load {
    Method originalMethod = class_getInstanceMethod(NSClassFromString(@"NSConcreteNotification"), @selector(dealloc));
    Method replacedMethod = class_getInstanceMethod(self, @selector(mydealloc));
    IMP imp1 = method_getImplementation(originalMethod);
    IMP imp2 = method_getImplementation(replacedMethod);
    // Set the implementation of dealloc to mydealloc
    method_setImplementation(originalMethod, imp2);
    // Add a mydealloc method to the NSConcreteNotification with the implementation as per the old dealloc method
    class_addMethod(NSClassFromString(@"NSConcreteNotification"), @selector(mydealloc), imp1, NULL);
    }

    - (void)mydealloc {
    NSLog(@"My concrete notification is being deallocated");
    NSLog(@"Name: %@\nObject: %@\nUser Info: %@\n", [self name], [self object], [self userInfo]);

    // Call the original method, whose implementation was exchanged with our own.
    // Note:  this ISN'T a recursive call, because this method should have been called through dealloc.
    NSParameterAssert(_cmd == @selector(dealloc));
    [self mydealloc];
    }

    @end

    Regards.

    Gideon
    >
    > On iPhone and 64-bit Mac, the linker enforces internal classes more strictly. NSConcreteNotification is private, so you can't link to it or subclass it. You can still get the class via runtime introspection like NSClassFromString(), which for debugging purposes ought to be good enough.
    > --
    > Greg Parker    <gparker...>    Runtime Wrangler
  • On 2010 Mar 11, at 18:00, Gideon King wrote:

    > This is really cool...so I can replace a method without being a subclass or category.

    I just need to chime in here, in case anyone missed it, to emphasize how *ALL-CAPS COOL* this is indeed.

    In the ReadMe of Apple's MethodReplacement.zip sample code, it states "The trick is to define a category on the class whose method you want to replace."  We have just learned that this sentence is incorrect.  You do *not* need to define a category on the class whose method you want to replace.

    As Gideon showed in his code, the first argument of class_getInstanceMethod() need *not* be self, and therefore you can replace any method in *any* class, even a private one that's unknown to the SDK, with any other method in *any class*, even a different class.

    This makes Method Replacement much more powerful in debugging, and even patching bugs in Cocoa.

    I just sent Apple a "wasn't helpful" gram on that ReadMe.

    Thanks, Gideon.  I hope you've found out who's sending that notification releasing your moc :)
  • Thanks Jerry, I should mention that I did get this caveat note from Greg Parker (Apple's "runtime wrangler"):

    "..you must be cautious about what your replacement method does. In particular, you must not use any of the class's ivars and you must not call [super something]. As long as you follow those, you can swap in a method from any class, or even a C function with a matching argument list."

    So as long as you are careful about what you do, yes it is entirely possible to replace methods at will. I hope nobody misuses it, but it certainly was essential in my debugging - yes, I found it! Yay!

    Essentially what appeared to be happening was that I had a notification queued which had an object in it which held a reference to a managed object, but by the time the notification was destroyed, the managed object had turned to a fault (but was not released because the notification was causing it to be retained), and the managed object context had been released as part of the document deallocation, so when my object tried to access the managed object, it tried to fire the fault on a freed managed object context...or something like that. Even if that's not exactly right in every detail, I now have the conceptual understanding of the problem and believe I will be able to fix it. What a relief!

    Regards

    Gideon

    On 12/03/2010, at 1:54 PM, Jerry Krinock wrote:

    >
    > On 2010 Mar 11, at 18:00, Gideon King wrote:
    >
    >> This is really cool...so I can replace a method without being a subclass or category.
    >
    > I just need to chime in here, in case anyone missed it, to emphasize how *ALL-CAPS COOL* this is indeed.
    >
    > In the ReadMe of Apple's MethodReplacement.zip sample code, it states "The trick is to define a category on the class whose method you want to replace."  We have just learned that this sentence is incorrect.  You do *not* need to define a category on the class whose method you want to replace.
    >
    > As Gideon showed in his code, the first argument of class_getInstanceMethod() need *not* be self, and therefore you can replace any method in *any* class, even a private one that's unknown to the SDK, with any other method in *any class*, even a different class.
    >
    > This makes Method Replacement much more powerful in debugging, and even patching bugs in Cocoa.
    >
    > I just sent Apple a "wasn't helpful" gram on that ReadMe.
    >
    > Thanks, Gideon.  I hope you've found out who's sending that notification releasing your moc :)
    >
  • On Mar 11, 2010, at 8:58 PM, Gideon King wrote:

    > So as long as you are careful about what you do, yes it is entirely possible to replace methods at will. I hope nobody misuses it, but it certainly was essential in my debugging - yes, I found it! Yay!

    Where "careful about what you do" includes "only use this in debugging code or non-production instrumentation", sure....

    Do *not* ship such shenanigans in production code.  One software update later and suddenly the presumptions made about that internal method you are replacing/overriding/augmenting are no longer true and *boom*.

    (been there, done that, the scars run deep -- seriously, don't ship it that way. :)

    b.bum
  • Method exchange is dangerous because if the target class (NSConcreteNotification) does not override the target function (dealloc), you may exchange it's superclass dealloc method instead and may end up overriding a method in a base class.
    Use it with great care and avoid it in production code if possible.

    Le 12 mars 2010 à 03:00, Gideon King a écrit :

    > This is really cool...so I can replace a method without being a subclass or category.
    >
    > So I implemented an NSObject subclass with the new method, and did the method exchange, and my new method was called instead of the old one, but I had a problem - the call to the switched out method (dealloc from the NSConcreteNotification) didn't work. I assumed that was because the old implementation of dealloc was now moved to my class, so I changed the method switching so that it ensures that the old dealloc is replaced with the implementation of mydealloc, and that I have added a method to the NSConcreteNotification which is called mydealloc, and does the things that the old dealloc did.
    >
    > This seems to work, so I thought I'd post it here in case anyone else needs to replace a method in a private class, and be able to call the original implementation.
    >
    > #import <objc/runtime.h>
    > #import <Cocoa/Cocoa.h>
    >
    > @interface MyConcreteNotification : NSObject {
    > }
    >
    > - (void)mydealloc;
    >
    > @end
    >
    > @implementation MyConcreteNotification
    >
    > + (void)load {
    > Method originalMethod = class_getInstanceMethod(NSClassFromString(@"NSConcreteNotification"), @selector(dealloc));
    > Method replacedMethod = class_getInstanceMethod(self, @selector(mydealloc));
    > IMP imp1 = method_getImplementation(originalMethod);
    > IMP imp2 = method_getImplementation(replacedMethod);
    > // Set the implementation of dealloc to mydealloc
    > method_setImplementation(originalMethod, imp2);
    > // Add a mydealloc method to the NSConcreteNotification with the implementation as per the old dealloc method
    > class_addMethod(NSClassFromString(@"NSConcreteNotification"), @selector(mydealloc), imp1, NULL);
    > }
    >
    > - (void)mydealloc {
    > NSLog(@"My concrete notification is being deallocated");
    > NSLog(@"Name: %@\nObject: %@\nUser Info: %@\n", [self name], [self object], [self userInfo]);
    >
    > // Call the original method, whose implementation was exchanged with our own.
    > // Note:  this ISN'T a recursive call, because this method should have been called through dealloc.
    > NSParameterAssert(_cmd == @selector(dealloc));
    > [self mydealloc];
    > }
    >
    > @end
    >
    >
    > Regards.
    >
    > Gideon
    >>
    >> On iPhone and 64-bit Mac, the linker enforces internal classes more strictly. NSConcreteNotification is private, so you can't link to it or subclass it. You can still get the class via runtime introspection like NSClassFromString(), which for debugging purposes ought to be good enough.
    >> --
    >> Greg Parker    <gparker...>    Runtime Wrangler


    -- Jean-Daniel
  • Wow, that's really, really awesome.

    Do you have any more details on why you can't use ivars? Not calling
    super makes sense. Can you access properties?
    On Mar 11, 2010, at 8:58 PM, Gideon King wrote:

    > Thanks Jerry, I should mention that I did get this caveat note from
    > Greg Parker (Apple's "runtime wrangler"):
    >
    > "..you must be cautious about what your replacement method does. In
    > particular, you must not use any of the class's ivars and you must
    > not call [super something]. As long as you follow those, you can
    > swap in a method from any class, or even a C function with a
    > matching argument list."
    >
    > So as long as you are careful about what you do, yes it is entirely
    > possible to replace methods at will. I hope nobody misuses it, but
    > it certainly was essential in my debugging - yes, I found it! Yay!
    >
    > Essentially what appeared to be happening was that I had a
    > notification queued which had an object in it which held a reference
    > to a managed object, but by the time the notification was destroyed,
    > the managed object had turned to a fault (but was not released
    > because the notification was causing it to be retained), and the
    > managed object context had been released as part of the document
    > deallocation, so when my object tried to access the managed object,
    > it tried to fire the fault on a freed managed object context...or
    > something like that. Even if that's not exactly right in every
    > detail, I now have the conceptual understanding of the problem and
    > believe I will be able to fix it. What a relief!
    >
    > Regards
    >
    > Gideon
    >
    > On 12/03/2010, at 1:54 PM, Jerry Krinock wrote:
    >
    >>
    >> On 2010 Mar 11, at 18:00, Gideon King wrote:
    >>
    >>> This is really cool...so I can replace a method without being a
    >>> subclass or category.
    >>
    >> I just need to chime in here, in case anyone missed it, to
    >> emphasize how *ALL-CAPS COOL* this is indeed.
    >>
    >> In the ReadMe of Apple's MethodReplacement.zip sample code, it
    >> states "The trick is to define a category on the class whose method
    >> you want to replace."  We have just learned that this sentence is
    >> incorrect.  You do *not* need to define a category on the class
    >> whose method you want to replace.
    >>
    >> As Gideon showed in his code, the first argument of
    >> class_getInstanceMethod() need *not* be self, and therefore you can
    >> replace any method in *any* class, even a private one that's
    >> unknown to the SDK, with any other method in *any class*, even a
    >> different class.
    >>
    >> This makes Method Replacement much more powerful in debugging, and
    >> even patching bugs in Cocoa.
    >>
    >> I just sent Apple a "wasn't helpful" gram on that ReadMe.
    >>
    >> Thanks, Gideon.  I hope you've found out who's sending that
    >> notification releasing your moc :)
    >>

  • *Why* is there no supported way to replace (or, rather, augment) methods in a class which one cannot override because one does not create instances of that class oneself?  It seems to me that this is just as legitimate as putting a category method on that class , but the latter approach is often useless as one cannot call 'super' (and again, why not?)

    As for something like this breaking after a software update, it seems to me that this is likely only when a major new release comes out and, since there are _always_ issues with major new releases of OS X it doesn't actually worry me all that much.

    Paul Sanders.

    ----- Original Message -----
    From: "Bill Bumgarner" <bbum...>
    To: "Gideon King" <gideon...>
    Cc: "Cocoa Dev" <cocoa-dev...>
    Sent: Friday, March 12, 2010 6:36 AM
    Subject: Re: Switching methods in private classes in Apple
    frameworks

    Where "careful about what you do" includes "only use this in debugging code or non-production instrumentation", sure....

    Do *not* ship such shenanigans in production code.  One software update later and suddenly the presumptions made about that internal method you are replacing/overriding/augmenting are no longer true and *boom*.
  • You can access ivars

    just
    #include "objc/objc-class.h"

    and then use
    static Ivar theIvar;  //create a static to cache the ivar information
    if (!theIvar) theIvar = class_getInstanceVariable([self class], "ivarName"); //look up the ivar information and cache it

    id aValue = object_getIvar(self, ivar);  //get the ivar
    object_setIvar(self,ivar,value);    //this is an assignment, you are responsible for memory management!

    There are a few gotchas if your ivar is not an NSObject .

    For sending a message to the super.
    [super amessage] won't work because super is  compile and will referred not to the super of self, but the super of the class containing the swizzled in message

    However the runtime can come to the rescue again by looking up the real super of self and sending a message to it.

    objc_msgSendSuper(&(struct objc_super){self, class_getSuperclass([self class])},_cmd, ...)

    set up a macro
    #define SUPER(...)  objc_msgSendSuper(&(struct objc_super){self, class_getSuperclass([self class])},_cmd, ##__VA_ARGS__)

    and then
    SUPER(my,argument,List);

    of course this is assuming you are getting a void or NSObject * reuturn, if a different return type, you have to change things up.

    /* insert warnings about how method swizzling is evil and should never be used, especially in production code, and how civilization as we know it will collapse if you use it.*/

    On 2010-03-12, at 5:36 AM, Daniel DeCovnick wrote:

    > Wow, that's really, really awesome.
    >
    > Do you have any more details on why you can't use ivars? Not calling super makes sense. Can you access properties?
    > On Mar 11, 2010, at 8:58 PM, Gideon King wrote:
    >
    >> Thanks Jerry, I should mention that I did get this caveat note from Greg Parker (Apple's "runtime wrangler"):
    >>
    >> "..you must be cautious about what your replacement method does. In particular, you must not use any of the class's ivars and you must not call [super something]. As long as you follow those, you can swap in a method from any class, or even a C function with a matching argument list."
    >>
    >> So as long as you are careful about what you do, yes it is entirely possible to replace methods at will. I hope nobody misuses it, but it certainly was essential in my debugging - yes, I found it! Yay!
    >>
    >> Essentially what appeared to be happening was that I had a notification queued which had an object in it which held a reference to a managed object, but by the time the notification was destroyed, the managed object had turned to a fault (but was not released because the notification was causing it to be retained), and the managed object context had been released as part of the document deallocation, so when my object tried to access the managed object, it tried to fire the fault on a freed managed object context...or something like that. Even if that's not exactly right in every detail, I now have the conceptual understanding of the problem and believe I will be able to fix it. What a relief!
    >>
    >> Regards
    >>
    >> Gideon
    >>
    >> On 12/03/2010, at 1:54 PM, Jerry Krinock wrote:
    >>
    >>>
    >>> On 2010 Mar 11, at 18:00, Gideon King wrote:
    >>>
    >>>> This is really cool...so I can replace a method without being a subclass or category.
    >>>
    >>> I just need to chime in here, in case anyone missed it, to emphasize how *ALL-CAPS COOL* this is indeed.
    >>>
    >>> In the ReadMe of Apple's MethodReplacement.zip sample code, it states "The trick is to define a category on the class whose method you want to replace."  We have just learned that this sentence is incorrect.  You do *not* need to define a category on the class whose method you want to replace.
    >>>
    >>> As Gideon showed in his code, the first argument of class_getInstanceMethod() need *not* be self, and therefore you can replace any method in *any* class, even a private one that's unknown to the SDK, with any other method in *any class*, even a different class.
    >>>
    >>> This makes Method Replacement much more powerful in debugging, and even patching bugs in Cocoa.
    >>>
    >>> I just sent Apple a "wasn't helpful" gram on that ReadMe.
    >>>
    >>> Thanks, Gideon.  I hope you've found out who's sending that notification releasing your moc :)
    >>>


previous month march 2010 next month
MTWTFSS
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31        
Go to today