0

I'm playing around with swizzling, and can't quite figure this out. My alloc swizzle looks like this:

@interface UIAlertView (Custom)
+ (id)allocCustom;
@end

@implementation UIAlertView (Custom)    
+ (void)load
{
    Method original;
    Method mock;
    original = class_getClassMethod(self, @selector(alloc));
    mock = class_getClassMethod(self, @selector(allocCustom));
    method_exchangeImplementations(mock, original);
}

+ (id)allocCustom
{
    NSLog(@"Custom!");
    return [self allocCustom];
}    
@end

If I pause at the NSLog statement, which gets called repeatedly, in the call stack I see:

* thread #1: tid = 0x2c388a, 0x00003a2f My App`+[UIAlertView(self=0x03825c74, _cmd=0x0327e663) allocCustom] + 31 at MYClass.m:62, queue = 'com.apple.main-thread, stop reason = breakpoint 4.1
    frame #0: 0x00003a2f My App`+[UIAlertView(self=0x03825c74, _cmd=0x0327e663) allocCustom] + 31 at iBFGClientAppDelegate.m:62
    frame #1: 0x036c7b42 CoreFoundation`+[NSTimeZone timeZoneWithName:] + 34
    frame #2: 0x036c7a52 CoreFoundation`+[NSTimeZone systemTimeZone] + 626
    frame #3: 0x036c7767 CoreFoundation`+[NSTimeZone defaultTimeZone] + 71
    frame #4: 0x036e82cd CoreFoundation`CFTimeZoneCopyDefault + 45
    frame #5: 0x036f8b90 CoreFoundation`CFCalendarCreateWithIdentifier + 544
    frame #6: 0x03705f2c CoreFoundation`__CFLogCString + 124
    frame #7: 0x03705e6e CoreFoundation`_CFLogvEx + 270
    frame #8: 0x01581fbc Foundation`NSLogv + 137
    frame #9: 0x01581f28 Foundation`NSLog + 27
    frame #10: 0x00003a3a My App`+[UIAlertView(self=0x03825c74, _cmd=0x0327e663) allocCustom] + 42 at MYClass.m:62
    frame #11: 0x036c7b42 CoreFoundation`+[NSTimeZone timeZoneWithName:] + 34
    frame #12: 0x036c7a52 CoreFoundation`+[NSTimeZone systemTimeZone] + 626
    frame #13: 0x036c7767 CoreFoundation`+[NSTimeZone defaultTimeZone] + 71
    frame #14: 0x036e82cd CoreFoundation`CFTimeZoneCopyDefault + 45
    frame #15: 0x036f8b90 CoreFoundation`CFCalendarCreateWithIdentifier + 544
    frame #16: 0x03705f2c CoreFoundation`__CFLogCString + 124
    frame #17: 0x03705e6e CoreFoundation`_CFLogvEx + 270
    frame #18: 0x01581fbc Foundation`NSLogv + 137
    frame #19: 0x01581f28 Foundation`NSLog + 27
    frame #20: 0x00003a3a My App`+[UIAlertView(self=0x01800f3c, _cmd=0x0327e663) allocCustom] + 42 at MYClass.m:62
    frame #21: 0x023fb8ae UIKit`UIApplicationMain + 60
    frame #22: 0x0000293b My App`main(argc=1, argv=0xbfffedac) + 75 at main.m:18

It seems as though timeZoneWithName: is calling back into my custom alloc method. Clearly there is no recursion when alloc is not swizzled, but I'm confused what's happening here.

Ben Flynn
  • 18,524
  • 20
  • 97
  • 142
  • 1
    allocCustom has been swizzled. in this context it calls the real implementation of alloc. – Ben Flynn Feb 14 '14 at 19:10
  • 1
    @rmaddy He's swizzling the methods, so it's actually calling the real `alloc`, or supposed to be at least. – Gavin Feb 14 '14 at 19:10
  • The problem is due to `alloc` being in `NSObject`, not `UIAlertView`. Read my answer. – Gavin Feb 14 '14 at 19:22

2 Answers2

2

Your problem is most likely occurring because you're swizzling a method allocCustom on UIAlertView with the method alloc, which is a method on NSObject. From what I have found, you can successfully swizzle NSObject's alloc method, but it doesn't appear you can do this from a subclass. You can see evidence that this is the case by adding the following to your UIAlertView category:

+ (id)alloc
{
    return [super alloc];
}

By doing that, it will now work correctly. But since alloc is a method on NSObject and clearly not being overridden by UIAlertView anyway, you could just remove your method swizzling, and add your code to your overridden alloc method:

+ (id)alloc
{
    NSLog(@"Custom!");
    return [super alloc];
}

Now this might be a little dangerous to do in case Apple at a future time changed their implementation and for some reason were overriding alloc in UIAlertView. In that case, I think your implementation would override theirs, although I think the exact behavior is technically undefined.

Gavin
  • 8,204
  • 3
  • 32
  • 42
  • Yes! I should have realized this. If you look at the pointer for the implementation of alloc, it will be the same as the super class so I'm really swizzling NSObject's alloc, which is not what I'm trying to do. Thanks! – Ben Flynn Feb 14 '14 at 19:24
  • 4
    You can use class_replaceMethod() to replace a method implementation or add a new one if that class does not already have an implementation of its own. – Greg Parker Feb 14 '14 at 19:25
  • @BenFlynn It's also why, when I copied your code, doing this swizzling was causing the program to crash immediately upon launch instead of only once a `UIAlertView` was going to be shown. – Gavin Feb 14 '14 at 19:25
  • It's still a bit unclear **why** the **recursion** occurred. It seems obvious that calling through to the original implementation (now named `allocCustom`) would not work because the selector is not in NSObject's interface. But I would assume some 'does not respond to selector' error. Recursion seem counter intuitive. – Nikolai Ruhe Feb 14 '14 at 19:31
  • @NikolaiRuhe: allocCustom calls NSLog, and NSLog allocates a NSTimeZone (see stack backtrace), which calls allocCustom again. – Martin R Feb 14 '14 at 19:34
  • @NikolaiRuhe I was seeing `doesNotRecognizeSelector:` in my stack. I think upon trying to allocate the first object in the program, it called the `allocCustom` implementation in the `UIAlertView` category, then it tried and failed to call the original `alloc` implementation, tried to throw an exception, and went thru the cycle again while trying to allocate the exception. – Gavin Feb 14 '14 at 19:34
0

I'm no expert in method swizzling, but I'm guessing that because your allocCustom is a class method, self is actually referring to the class itself. What you really want is something like this

+(id) allocCustom {

 id obj = calloc(class_getInstanceSize(self), 1);
 obj->isa = self;
 obj->retainCount = 1;
 return obj;
}

this link may be of use to you: https://www.mikeash.com/pyblog/friday-qa-2013-01-25-lets-build-nsobject.html

Ben Pious
  • 4,765
  • 2
  • 22
  • 34
  • alloc is also a class method, so I don't see what that should be a problem. I tried the code out of curiosity, but it borks bridging from the void pointer created by calloc. – Ben Flynn Feb 14 '14 at 19:20