Peter Steinberger

How to Inspect the View Hierarchy of Third-Party Apps

I’m generally not a big fan of jailbreaks. Mostly this is because they’re used for piracy and all the hacks result in weird crashes that generally are impossible to reproduce. Still, I was quite excited about the recent iOS 7 jailbreak, since it enables us to attach the debugger to third-party apps and do a little bit of runtime analysis.

Why? Because it’s fun, and it can inspire you to solve things differently. Studying the view hierarchy of complex apps can be rather revealing and it’s interesting to see how others are solving similar problems. This was one thing that took many iterations to get right in PSPDFKit, our iOS PDF framework.

So, how does this work? It’s actually super simple.

  1. Jailbreak your device of choice. I’ve used an iPad 4 here. Make sure it runs iOS 7.0.x. Both arm7(s) and arm64 devices will work now. Don’t jailbreak a device that’s used in production. Otherwise you lose a lot of security features as well. I only jailbreak a clean device and install some apps to inspect.

  2. Open Cydia and install OpenSSH, nano, and Cydia Substrate (previously called MobileSubstrate).

  3. Copy the Reveal library. Find out the device IP address via Settings and execute the following in your terminal (this assumes you have installed Reveal already):
    scp -r /Applications/Reveal.app/Contents/SharedSupport/iOS-Libraries/Reveal.framework root@192.168.0.X:/System/Library/Frameworks
    scp /Applications/Reveal.app/Contents/SharedSupport/iOS-Libraries/libReveal.dylib root@192.168.0.X:/Library/MobileSubstrate/DynamicLibraries.
    For Spark Inspector, you would use scp "/Applications/Spark Inspector.app/Contents/Resources/Frameworks/SparkInspector.dylib" root@192.168.0.X:/Library/MobileSubstrate/DynamicLibraries
    Note: The default SSH password on iOS is ‘alpine.’

  4. SSH into the device and create following text file with nano /Library/MobileSubstrate/DynamicLibraries/libReveal.plist: { Filter = { Bundles = ( "<App ID>" ); }; }
    Previously, this worked with wildcard IDs, but this approach has problems with the updated Cydia Substrate. So simply add the App ID of the app you want to inspect, and then restart the app.

  5. Respring with killall SpringBoard or simply restart the device.

Done! Start your app of choice and select it in Reveal. (This should also work similary for SparkInspector.) Attaching via LLDB is a bit harder and I won’t go into details here since this could also be used to pirate apps. Google for ‘task_for_pid-allow,’ ‘debugserver,’ and ‘ldid’ if you want to try this.

Fixing UISearchDisplayController on iOS 7

iOS 7 is great, but it’s still very much a 1.0. I’ve spent a lot of time working around iOS 7-specific bugs in PSPDFKit and will share some of my work here.

This is how UISearchDisplayController looks on iOS 7:

Pretty bad, eh? This is how it should look:

Here’s the code to fix it. It doesn’t use private API (although there is some view hierarchy fighting), and it’s fairly clean. It uses my UIKit legacy detector, so this code won’t do any harm on iOS 5/6:

I imagine one could subclass UISearchDisplayController directly and internally forward the delegate to better package this fix, but I only need it in one place so the UIViewController was a good fit.

Note that this uses some awful things like dispatch_async(dispatch_get_main_queue(), but it shouldn’t do any harm even if Apple fixes its controller sometime in the future.

Smart Proxy Delegation

When calling optional delegates, the regular pattern is to check using respondsToSelector:, then actually call the method. This is straightforward and easy to understand:

1
2
3
4
id<PSPDFResizableViewDelegate> delegate = self.delegate;
if ([delegate respondsToSelector:@selector(resizableViewDidBeginEditing:)]) {
    [delegate resizableViewDidBeginEditing:self];
}

Now, this used to be three lines and now it’s four lines, because delegate is usually weak, and once you enable the relatively new Clang warning, -Warc-repeated-use-of-weak, you will get a warning when accessing self.delegate more than once. All in all, that’s a lot of boilerplate for a simple selector call.

In the past, I’ve used an approach similar to what Apple does with parsing the delegate when it’s being set and caching the respondsToSelector: calls. But that’s even more boilerplate, cumbersome to update once you change a delegate, and really not worth it unless you call your delegates 100x per second.

What we really want is something like this:

1
[self.delegateProxy resizableViewDidBeginEditing:self];

We can use NSProxy to simply forward the method if it’s implemented. This used to be expensive, but with Leopard Apple came Fast Message Forwarding. This “can be an order of magnitude faster than regular forwarding” and doesn’t require building an NSInvocation-object.

Our custom NSProxy is really quite simple and just a few lines of code. The important part is here:

1
2
3
4
5
6
7
8
9
10
11
12
13
- (BOOL)respondsToSelector:(SEL)selector {
    return [self.delegate respondsToSelector:selector];
}
- (id)forwardingTargetForSelector:(SEL)selector {
    id delegate = self.delegate;
    return [delegate respondsToSelector:selector] ? delegate : self;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    return [self.delegate methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation {
    // ignore
}

One sad detail is that if the method is not implemented in the delegate, message forwarding will use the slow path with methodSignatureForSelector: and forwardInvocation:, because forwardingTargetForSelector: interprets both ‘nil’ and ‘self’ as ‘continue with message forwarding.’

Implementing The Delegate Proxy

We want to make sure that implementing this in our classes is simple. Here’s the pattern I use in PSPDFKit:

1
2
3
4
5
6
7
8
9
@interface PSPDFResizableView ()
@property (nonatomic, strong) id <PSPDFResizableViewDelegate> delegateProxy;
@end

PSPDF_DELEGATE_PROXY(id <PSPDFResizableViewDelegate>)

- (void)handleRecognizerStateBegan:(UIGestureRecognizer *)recognizer {
    [self.delegateProxy resizableViewDidBeginEditing:self];
}

You’ll notice a few things. First, we lie about the actual type of delegateProxy. It’s a class of type PSTDelegateProxy, but we declare it as the delegate type so that we don’t have to cast it every time we use it, and so that we also get compiler-checked warnings when there are typos in the selector. Notice that the proxy needs to be strongly retained.

Second, we’re using a macro to simplify things. This macro expands into following:

1
2
3
4
5
6
- (void)setDelegate:(id <PSTDelegate>)delegate {
  self.delegateProxy = delegate ? (id <PSTDelegate>)[[PSPDFDelegateProxy alloc] initWithDelegate:delegate] : nil;
}
- (id <PSTDelegate>)delegate {
  return ((PSPDFDelegateProxy *)self.delegateProxy).delegate;
}

We keep the weak reference of the delegate directly in the PSPDFDelegateProxy; no need to save it twice. This macro only works if you name your delegate delegate, but that should be the common case, and you could expand the macro to cover different cases. We do keep a strong reference of our proxy-object around, but this won’t hurt. Other solutions work with weak-saving NSProxy, but that’s not needed and also buggy on iOS 5.

Handling Defaults

Now we’ve already covered most of the cases. But there’s one pattern that we still need to take care of, which is optional methods that change a default return value if implemented:

1
2
3
4
5
_acceptedStartPoint = YES;
id<PSPDFSelectionViewDelegate> delegate = self.delegate;
if ([delegate respondsToSelector:@selector(selectionView:shouldStartSelectionAtPoint:)]) {
    _acceptedStartPoint = [delegate selectionView:self shouldStartSelectionAtPoint:location];
}

If the proxy can’t find a delegate, it will return nil (id), NO (BOOL), 0 (int), or 0.f (float). Sometimes we want a different default. But again, we can perfectly solve this with some more NSProxy trickery:

1
_acceptedStartPoint = [[(id)self.delegateProxy YESDefault] selectionView:self shouldStartSelectionAtPoint:location];

And here’s the relevant code for the subclass that is being returned after calling YESDefault:

1
2
3
4
5
6
7
- (void)forwardInvocation:(NSInvocation *)invocation {
    // If method is a BOOL, return YES.
    if (strncmp(invocation.methodSignature.methodReturnType, @encode(BOOL), 1) == 0) {
        BOOL retValue = YES;
        [invocation setReturnValue:&retValue];
    }
}

We have to to go down to NSInvocation, which is a bit slower, but again, you won’t notice, except your delegate is called many times per second, which is quite unusual.

All code (including test cases) is on GitHub.

I’ve already implemented this almost everywhere in my iOS PDF Framework and was able to delete a lot of boilerplate code. I’m @steipete on Twitter. Looking forward to your feedback.

Adding Keyboard Shortcuts to UIAlertView

I’m not even home from the more-than-excellent NSConference in Leicester, but had to hack on something super awesome that Evan Doll of Flipboard presented earlier today. They added keyboard support to UIAlertView and UIActionSheet (amongst other things) – simply to make debugging in the Simulator faster by accepting Esc and Enter keys – something that Apple should have done anyway. There’s not much value in shipping this in release builds, except better support for bluetooth keyboards. And since this hack uses private API AND accesses a struct with a memory layout that could change, I don’t recommend shipping it. If you do, make sure that you whitelist iOS versions and block this method by default on unknown future versions of iOS. I’m using it in PSPDFKit only when compiled in DEBUG mode for the Simulator.

The actual hack is mostly based on this blog post about intercepting the keyboard on iOS, and it’s not pretty. I had to modify some constants to make it work on iOS 5/6:

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#define GSEVENT_TYPE 2
#define GSEVENT_FLAGS 12
#define GSEVENTKEY_KEYCODE 15
#define GSEVENT_TYPE_KEYUP 10
// http://nacho4d-nacho4d.blogspot.co.uk/2012/01/catching-keyboard-events-in-ios.html
__attribute__((constructor)) static void PSPDFKitAddKeyboardSupportForUIAlertView(void) {
    @autoreleasepool {
        // Hook into sendEvent: to get keyboard events.
        SEL sendEventSEL = NSSelectorFromString(@"pspdf_sendEvent:");
        IMP sendEventIMP = imp_implementationWithBlock(^(id _self, UIEvent *event) {
            objc_msgSend(_self, sendEventSEL, event); // call original implementation.

            SEL gsEventSEL = NSSelectorFromString([NSString stringWithFormat:@"%@%@Event", @"_", @"gs"]);
            if ([event respondsToSelector:gsEventSEL]) {
                // Key events come in form of UIInternalEvents.
                // They contain a GSEvent object which contains a GSEventRecord among other things.
                int *eventMem = (int *)[event performSelector:gsEventSEL];
                if (eventMem) {
                    if (eventMem[GSEVENT_TYPE] == GSEVENT_TYPE_KEYUP) {
                        UniChar *keycode = (UniChar *)&(eventMem[GSEVENTKEY_KEYCODE]);
                        int eventFlags = eventMem[GSEVENT_FLAGS];
                        //NSLog(@"Pressed %d", *keycode);
                        [[NSNotificationCenter defaultCenter] postNotificationName:@"PSPDFKeyboardEventNotification" object:nil userInfo: @{@"keycode" : @(*keycode), @"eventFlags" : @(eventFlags)}];
                    }
                }
            }
        });
        PSPDFReplaceMethod(UIApplication.class, @selector(sendEvent:), sendEventSEL, sendEventIMP);

        // Add keyboard handler for UIAlertView.
        SEL didMoveToWindowSEL = NSSelectorFromString(@"pspdf_didMoveToWindow");
        IMP didMoveToWindowIMP = imp_implementationWithBlock(^(UIAlertView *_self, UIEvent *event) {
            objc_msgSend(_self, didMoveToWindowSEL, event); // call original implementation.

            static char kPSPDFKeyboardEventToken;
            if (_self.window) {
                id observerToken = [[NSNotificationCenter defaultCenter] addObserverForName:@"PSPDFKeyboardEventNotification" object:nil queue:nil usingBlock:^(NSNotification *notification) {
                    NSUInteger keyCode = [notification.userInfo[@"keycode"] integerValue];
                    if (keyCode == 41) /* ESC */ {
                        [_self dismissWithClickedButtonIndex:_self.cancelButtonIndex animated:YES];
                    }else if (keyCode == 40) /* ENTER */ {
                        [_self dismissWithClickedButtonIndex:_self.numberOfButtons-1 animated:YES];
                    }
                }];
                objc_setAssociatedObject(_self, &kPSPDFKeyboardEventToken, observerToken, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
            }else {
                id observerToken = objc_getAssociatedObject(_self, &kPSPDFKeyboardEventToken);
                if (observerToken) [[NSNotificationCenter defaultCenter] removeObserver:observerToken];
            }
        });
        PSPDFReplaceMethod(UIAlertView.class, @selector(didMoveToWindow), didMoveToWindowSEL, didMoveToWindowIMP);
    }
}

You will also need some swizzling helpers. Here’s what I use:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// http://www.mikeash.com/pyblog/friday-qa-2010-01-29-method-replacement-for-fun-and-profit.html
static void PSPDFSwizzleMethod(Class c, SEL orig, SEL new) {
    Method origMethod = class_getInstanceMethod(c, orig);
    Method newMethod = class_getInstanceMethod(c, new);
    if (class_addMethod(c, orig, method_getImplementation(newMethod), method_getTypeEncoding(newMethod))) {
        class_replaceMethod(c, new, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
    }else {
        method_exchangeImplementations(origMethod, newMethod);
    }
}

void PSPDFReplaceMethod(Class c, SEL orig, SEL newSel, IMP impl) {
    Method method = class_getInstanceMethod(c, orig);
    if (!class_addMethod(c, newSel, impl, method_getTypeEncoding(method))) {
        PSPDFLogError(@"Failed to add method: %@ on %@", NSStringFromSelector(newSel), c);
    }else PSPDFSwizzleMethod(c, orig, newSel);
}

How to Center Content Within UIScrollView

There seems to be some confusion around the net when it comes to the best way to center a view within an UIScrollView.

And it’s actually not that easy to get right. In PSPDFKit version 1, I used the Apple-recommended way (see Apple’s photo example) of subclassing layoutSubviews. This was “good enough” until I added smart zoom in version 2 of my PDF framework. (That is, text blocks are detected and zoomed onto with a double tap, just like you’re used to in the mobile version of Safari). I noticed that the zoomToRect method did not work properly; the final rect was offset, and it was quite obvious why, since the layoutSubview-method moved around the frame of the view that was zoomed in on. And don’t try to compensate this moving – it seems relatively impossible to get right.

The next generation (PSPDFKit 2.2 and upward) used override setContentOffset: to always center the content. This works, but has the drawback that you can’t pan the view around when you’re zooming out more – it’s always fixed centered. This solution also has a very nasty hidden bug, where the UIScrollView can be “locked up” and doesn’t accept any touches anymore until you perform a pinch in/out. I’ve had Apple DTS against this and even the person helping me only came up with useless workarounds (like not using gesture recognizers… right.) It’s hard to reproduce this bug here without open sourcing half of my framework, so please just trust me on that one.

I’ve asked around on Twitter (thanks, everyone!) and most recommendations were about overriding layoutSubviews. Some suggested stuffing the view into another view and doing the centering manually (which I tried) but this has the drawback that you can scroll into the empty space when zoomed in (or you adapt the view and get the same problem as you had in layoutSubviews).

The solution that works best by far is by setting contentInset. It allows you to pan around the view when zoomed out and doesn’t expose the “lock” bug that overriding setContentOffset: had. It also works fine with using zoomToRect. There are a few gotchas when using edgeInsets as well – for example, I have a mechanism that preserves the current view state (page, zoom, contentOffset) and restores that later on. If you set contentOffset to 0,0 this will decenter your image (in contrast to methods one and two). But you can trigger re-centering with following trick:

1
2
3
        // Trigger a new centering (center will change the content offset)
        scrollView.contentInset = UIEdgeInsetsZero;
        [scrollView ensureContentIsCentered];

I’ve made a small GitHub project that shows off all three centering techniques and zooms to a small rect on double tap (to show off the zoomToRect problem). Hopefully this helps someone save a few hours!

UIAppearance for Custom Views

UIAppearance is hardly a new technology, since it was first introduced at WWDC 2011, but it still doesn’t have the adoption it deserves (guilty as charged here as well). Since most apps are IOS 5 only now, there’s no excuse anymore to not adopt it. Also, chances are quite high that at least some properties of your classes already support UIAppearance implicitly, since the preprocessor macro to ‘enable’ UIAppearance is actually defined to be empty:

#define UI_APPEARANCE_SELECTOR

In the simplest case, add UI_APPEARANCE_SELECTOR to your properties to inform others that this property can be set via an UIAppearance proxy. There are, however, some gotchas that are not clearly mentioned in the documentation, and it’s always interesting how something like this works behind the scenes. (This is not a tutorial – go ahead and read Apple’s documentation on UIAppearance if you’ve never used it before.)

From looking at the debugger, UIAppearance is quite smart and only applies properties before the view is added to a window:

UIAppearance is mostly for UIView subclasses, with some exceptions like UIBarItem (and UIBarButtonItem), which internally handle their respective views. For those classes, Apple implemented a custom appearance proxy (_UIBarItemAppearance).

When a custom appearance is set, _UIAppearanceRecorder will track the customizations. There are also certain optimized appearance storage classes like (_UISegmentedControlAppearanceStorage) for UISegmentedControl or _UINavigationBarAppearanceStorage for UINavigationBar.

Let’s start with a simple example, converting this class (taken from my iOS PDF SDK) to work with UIAppearance:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/// Simple rounded label.
@interface PSPDFRoundedLabel : UILabel
/// Corner radius. Defaults to 10.
@property (nonatomic, assign) CGFloat cornerRadius;
/// Label background. Defaults to [UIColor colorWithWhite:0.f alpha:0.6f]
@property (nonatomic, strong) UIColor *rectColor;
@end

@implementation PSPDFRoundedLabel
- (id)initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {
        self.rectColor = [UIColor colorWithWhite:0.f alpha:0.6f];
        self.cornerRadius = 10.f;
    }
    return self;
}
- (void)setBackgroundColor:(UIColor *)color {
    [super setBackgroundColor:[UIColor clearColor]];
    self.rectColor = color;
}
// drawRect is trivial
@end

Simply adding UI_APPEARANCE_SELECTOR will not work here. One gotcha is that UIAppearance swizzles all setters that have a default apperance, and tracks when they get changed, so that UIAppearance doesn’t override your customizations. This is a problem here, since we use setters in the initializer, and for UIAppearance it now looks as though we already customized the class ourselves. Lesson: Only use direct ivar access in the initializer for properties that comply to UI_APPEARANCE_SELECTOR:

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
/// Simple rounded label.
@interface PSPDFRoundedLabel : UILabel
/// Corner radius. Defaults to 10.
@property (nonatomic, assign) CGFloat cornerRadius UI_APPEARANCE_SELECTOR;
/// Label background. Defaults to [UIColor colorWithWhite:0.f alpha:0.6f]
@property (nonatomic, strong) UIColor *rectColor UI_APPEARANCE_SELECTOR;
@end

@implementation PSPDFRoundedLabel {
    BOOL _isInitializing;
}
- (id)initWithFrame:(CGRect)frame {
    _isInitializing = YES;
    if (self = [super initWithFrame:frame]) {
        _rectColor = [UIColor colorWithWhite:0.f alpha:0.6f];
        _cornerRadius = 10.f;
    }
    _isInitializing = NO;
    return self;
}
- (void)setBackgroundColor:(UIColor *)color {
    [super setBackgroundColor:[UIColor clearColor]];
    // Check needed for UIAppearance to work (since UILabel uses setters in init)
    if (!_isInitializing) self.rectColor = color;
}
// drawRect is trivial
@end

This class now fully works with UIAppearance. Notice that we had to do some ugly state checking (_isInitializing), because UILabel internally calls self.backgroundColor = [UIColor whiteColor] in the init, which then calls the setRectColor, which would already could as “changed” for UIAppearance. Notice the TaggingApperanceGeneralSetterIMP that Apple uses to track any change to the setter:

I’m using the following code to test the customizations:

[[PSPDFRoundedLabel appearanceWhenContainedIn:[PSCThumbnailGridViewCell class], nil] setRectColor:[UIColor colorWithRed:0.165 green:0.226 blue:0.650 alpha:0.800]];
[[PSPDFRoundedLabel appearanceWhenContainedIn:[PSCThumbnailGridViewCell class], nil] setCornerRadius:2];

We can also use the runtime at any point to query what appearance settings there are for any given class. This is only meant to be used within the debugger, since it uses private API to query _UIAppearance:

po [[NSClassFromString(@”_UIAppearance”) _appearanceForClass:[PSPDFRoundedLabel class] withContainerList:@[[PSCThumbnailGridViewCell class]]] valueForKey:@”_appearanceInvocations”]
$0 = 0x0bd08cc0 <__NSArrayM 0xbd08cc0>(
<NSInvocation: 0xbd08a60>
return value: {v} void
target: {@} 0x0
selector: {:} _UIAppearance_setRectColor:
argument 2: {@} 0xbd08210
,
<NSInvocation: 0xbd09100>
return value: {v} void
target: {@} 0x0
selector: {:} _UIAppearance_setCornerRadius:
argument 2: {f} 0.000000
)

That’s it! The class is fully compatible with UIAppearance. When using this inside a framework, you should write custom UIAppearance rules instead of manually setting the property, to allow to override those rules from the outside (remember, manually setting a property will disable it for apperance usage). +load is a good time for that. There are some more gotchas on UIAppearance, like BOOL not being supported (use NSInteger instead), and some honorable exceptions that do support appearance selectors, like DACircularProgress.

Update: As of iOS 8, BOOL is now supported for UIAppearance.

Hacking Block Support Into UIMenuItem

tl;dr: UIMenuItem! Blocks! Get the code on GitHub.

While developing a new version of PSPDFKit, I started using UIMenuController more and more. The first thing you’ll notice is that it’s different from your typical target/action pattern, in that the target is missing:

1
[UIMenuItem alloc] initWithTitle:@"Title" action:@selector(menuItemAction:)];

This is actually pretty genius, in part. iOS checks if the @selector can be invoked calling canPerformAction:withSender:, which is part of UIResponder. If the object returns NO, the system walks up the UIResponder chain until it finds an object that returns YES or nextResponder returns nil.

A typical responder chain looks like this: UIView -> UIViewController -> UINavigationController -> UIApplication -> AppDelegate.

Now this is great in theory. For example, if you implementcopy: on your viewController, the firstResponder can be any subview and it still works. In practice, however, I found this more limiting and annoying. And I’m not the only one.

Especially if your menus get more complex, your code is littered with selectors. So let’s write a block-based subclass. Enter PSMenuItem:

1
2
3
PSMenuItem *actionItem = [[PSMenuItem alloc] initWithTitle:@"Action 1" block:^{
    NSLog(@"Hello, from a block!");
}];

My naive approach was to just use one common selector internally and execute the block that is saved in the PSMenuItem subclass. The only problem: (id)sender of the action gets called with the UIMenuController. This is wrong on so many levels, especially since UIMenuController is a singleton anyway. There’s no easy way to know what UIMenuItem has been pressed. But since I was already committed to writing the subclass, that couldn’t stop me. We just create a unique selector for each UIMenuItem, and catch execution at a lower level.

Enter Cocoa’s Message Forwarding

If the runtime can’t find a selector on the current class, message forwarding is started. (That’s the tech that allows classes like NSUndoManager or NSProxy.)

  1. Lazy method resolution: resolveInstanceMethod: is called. If this returns YES, message sending is restarted, as the system assumes that the method has been added at runtime. We could theoretically use this and add a method that calls the block at runtime. But this would pollute the object with many new methods – not what we want.

  2. Fast forwarding path: -(id)forwardingTargetForSelector:(SEL)sel has been added in Leopard as a faster approach to the NSInvocation-based message forwarding. We could use this to react to our custom selector, but we would have to return an object that implements our selector (or does not throw an exception with undefined methods.) Possible candidate, but there’s something better.

  3. Normal forwarding path: This is the “classic” message forwarding that has existed since the old days. And actually, two methods are called here: methodSignatureForSelector:, followed by forwardInvocation:. (The method signature is needed to build the NSInvocation.) PSMenuItem hooks into both of those methods. But let’s go step by step through PSMenuItem’s + (void)installMenuHandlerForObject:(id)object:

1
2
3
4
5
+ (void)installMenuHandlerForObject:(id)object {
    @autoreleasepool {
        @synchronized(self) {
            // object can be both a class or an instance of a class.
            Class objectClass = class_isMetaClass(object_getClass(object)) ? object : [object class];

Note the @synchronized; swizzling is not threadsafe. Also, we add an @autoreleasepool here, as this could be executed from +load or +initialize at a very early time when there’s no default NSAutoreleasePool in place yet.

class_isMetaClass checks if object_getClass returns a class or a metaclass. This is needed because “object” can both be an instance of a class or a class object itself, and you can’t just invoke an isKindOfClass on a Class object. If you’re wondering what a metaclass is, it’s basically a class that defines methods available on the class. CocoaWithLove has a great article on that.

1
2
3
4
5
6
7
8
9
10
11
12
        // check if menu handler has been already installed.
        SEL canPerformActionSEL = NSSelectorFromString(@"pspdf_canPerformAction:withSender:");
        if (!class_getInstanceMethod(objectClass, canPerformActionSEL)) {

            // add canBecomeFirstResponder if it is not returning YES. (or if we don't know)
            if (object == objectClass || ![object canBecomeFirstResponder]) {
                SEL canBecomeFRSEL = NSSelectorFromString(@"pspdf_canBecomeFirstResponder");
                IMP canBecomeFRIMP = imp_implementationWithBlock(PSPDFBlockImplCast(^(id _self) {
                    return YES;
                }));
                PSPDFReplaceMethod(objectClass, @selector(canBecomeFirstResponder), canBecomeFRSEL, canBecomeFRIMP);
            }

Here we test if the class has already been swizzled by us with using class_getInstanceMethod. Again, because object might be a Class already, we can’t just use respondsToSelector:. Next, we test if we should add a handler to canBecomeFirstResponder. This is needed to make the UIMenuController display in the first place.

Note the imp_implementationWithBlock. This is a new method in iOS 4.3 upward, but has a much nicer syntax and is more compact than classic C functions. There’s another small annoyance: PSPDFBlockImplCast. The syntax of imp_implementationWithBlock was slightly changed in yet-to-be released versions of Xcode. Older versions still need the (__bridge void *) cast; newer versions will complain and only work without.

PSPDFReplaceMethod is a helper that first adds the new method via our pspdf_ selector name, then swizzles the original implementation with our custom implementation:

1
2
3
4
5
6
            // swizzle canPerformAction:withSender: for our custom selectors.
            // Queried before the UIMenuController is shown.
            IMP canPerformActionIMP = imp_implementationWithBlock(PSPDFBlockImplCast(^(id _self, SEL action, id sender) {
                return PSIsMenuItemSelector(action) ? YES : ((BOOL (*)(id, SEL, SEL, id))objc_msgSend)(_self, canPerformActionSEL, action, sender);
            }));
            PSPDFReplaceMethod(objectClass, @selector(canPerformAction:withSender:), canPerformActionSEL, canPerformActionIMP);

Next up, we swizzle canPerformAction:withSender:. This is called before the UIMenuController is displayed. If we detect our custom selector (PSIsMenuItemSelector), we return YES, else we call the original implementation. Note the tricky casting on objc_msgSend. (We could also build an NSInvocation, but that would be much slower and needs much more code).

PSIsMenuItemSelector is just shorthand for return [NSStringFromSelector(selector) hasPrefix:kMenuItemTrailer];:

1
2
3
4
5
6
7
8
9
10
            // swizzle methodSignatureForSelector:.
            SEL methodSignatureSEL = NSSelectorFromString(@"pspdf_methodSignatureForSelector:");
            IMP methodSignatureIMP = imp_implementationWithBlock(PSPDFBlockImplCast(^(id _self, SEL selector) {
                if (PSIsMenuItemSelector(selector)) {
                    return [NSMethodSignature signatureWithObjCTypes:"v@:@"]; // fake it.
                }else {
                    return (NSMethodSignature *)objc_msgSend(_self, methodSignatureSEL, selector);
                }
            }));
            PSPDFReplaceMethod(objectClass, @selector(methodSignatureForSelector:), methodSignatureSEL, methodSignatureIMP);

Next, we arrive at the method that’s called during message forwarding, when the user selects a UIMenuItem. We again check for the selector and return a faked NSMethodSignature. If we wouldn’t return a signature here, we’d get a selector not implemented exception. "v@:@" is the selector encoding for -(void)action:(id)sender. v is the return type (void), the first @ is self, the : is the selector (_cmd), the @ finally is id sender. You can learn more on Apple Developer about objc type encodings.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
            // swizzle forwardInvocation:
            SEL forwardInvocationSEL = NSSelectorFromString(@"pspdf_forwardInvocation:");
            IMP forwardInvocationIMP = imp_implementationWithBlock(PSPDFBlockImplCast(^(id _self, NSInvocation *invocation) {
                if (PSIsMenuItemSelector([invocation selector])) {
                    for (PSMenuItem *menuItem in [UIMenuController sharedMenuController].menuItems) {
                        if ([menuItem isKindOfClass:[PSMenuItem class]] && sel_isEqual([invocation selector], menuItem.customSelector)) {
                            [menuItem performBlock]; break; // find corresponding MenuItem and forward
                        }
                    }
                }else {
                    objc_msgSend(_self, forwardInvocationSEL, invocation);
                }
            }));
            PSPDFReplaceMethod(objectClass, @selector(forwardInvocation:), forwardInvocationSEL, forwardInvocationIMP);
        }
    }
}
}

Finally, the last piece. After methodSignatureForSelector returns a valid NSMethodSignature, the system builds an NSInvocation object that we can handle (or not). Here we load the selector, loop through all menuItems in the UIMenuController, and finally call the block on the PSMenuItem, if found. Note that we could also extract UIMenuController from the NSInvocation itself, but since it’s a singleton, there’s no need for that.

One simple piece remains. We build up the custom selector in our initWithTitle:block:

1
2
3
4
5
6
// Create a unique, still debuggable selector unique per PSMenuItem.
NSString *strippedTitle = [[[title componentsSeparatedByCharactersInSet:[[NSCharacterSet letterCharacterSet] invertedSet]] componentsJoinedByString:@""] lowercaseString];
CFUUIDRef uuid = CFUUIDCreate(kCFAllocatorDefault);
NSString *uuidString = CFBridgingRelease(CFUUIDCreateString(kCFAllocatorDefault, uuid));
CFRelease(uuid);
SEL customSelector = NSSelectorFromString([NSString stringWithFormat:@"%@_%@_%@:", kMenuItemTrailer, strippedTitle, uuidString]);

I’ve even used a UUID to allow menu items with the same title; they otherwise would generate the same selector and would potentially call the wrong block.

Also, thanks to Mike Ash for his great Friday Q&A about Objective-C Message Forwarding.

Using Subscripting With Xcode 4.4 and iOS 4.3+

Now that Xcode 4.4 has finally reached Golden Master and you can submit apps, here’s a trick to use subscripting right now. Yes, Apple will introduce this feature in a future version of OS X and iOS, but why wait? Here’s the snippet from PSPDFKit, my iOS PDF framework, to make it work:

It’s a bit crude, and Xcode won’t show any warnings if you’re doing subscripting on any object now, but you know what you’re doing, right? Furthermore this is only temporary, to make it compile with Xcode 4.4. In Xcode 4.5, this snippet won’t do anything and isn’t needed anymore, since the new SDK already has those methods defined on the classes you care about. Just make sure this is in your global header file (e.g. precompiled headers).

Note that unlike literals, which are really just syntactical sugar and already work great in Xcode 4.4+ (LLVM 4.0+), subscripting actually calls into new methods. So how does this “magically defining headers” actually work?

Thanks to Mike Ash and Cédric Luthi, here’s the answer:

The subscripting compatibility shim is actually part of ARCLite. The ARCLite load function takes care of dynamically adding the four subscript methods with class_addMethod. The implementations of these methods simply call the equivalent non-subscript methods.

If you, for any reason, are not on ARC yet, you really want to force the compiler to link with libarclite using the -fobjc-arc linker flag. This works all the way back to iOS 4.3.

Also check out the new Xcode Refactoring Assistant to convert your codebase to Modern Objective-C. It’s awesome.

Want to know more tips and tricks about iOS? Follow @steipete on Twitter.

Pimping recursiveDescription

While working on PSPDFKit, more and more I embrace viewController containment to better distribute responsibilities between different view controllers. One thing that always annoyed me about that is that po [[UIWindow keyWindow] recursiveDescription] is less and less useful if you just see a big bunch of UIViews. I asked some engineers at WWDC if there’s something like recursiveDescription just for UIViewControllers, but they didn’t have a answer to that, so I finally wrote my own.

Regular output of po [[UIWindow keyWindow] recursiveDescription]:

Pimped output:

The solution is surprisingly simple. Add the following code somewhere to your project and you’re done. As a bonus, this also lists attached childViewControllers if you run iOS 5 or above, and it will show you if a childViewController has a greater frame than its parentViewController (this is usually an easy-to-miss bug).

Note the usage of __attribute__((constructor)). This will call the method at a very early stage on app loading (thus we need an explicit @autorelease pool):

This works with or without ARC, iOS 4.3 and above. (imp_implementationWithBlock requires iOS 4.3, unless you want to perform some dark magic.)

For those if you that are curious, I use a private API call (_viewDelegate), but that’s obfuscated (it would pass an AppStore review) and you really only should compile that function in debug mode anyway.

NSURLCache Uses a Disk Cache as of iOS 5

While writing AFDownloadRequestOperation, a new subclass for AFNetworking, I discovered that the behavior of NSURLCache changed between iOS 4.x and iOS 5.x.

Before iOS 5, NSURLCache just saved requests to memory, even if the documentation said otherwise – the diskCapacity property was silently ignored. This led to some open-source subclasses of NSURLCache, which retrofit disk caching. Most popular is SDURLCache and my enhanced, faster fork of it. Even Apple has an example online that shows how to create a simple URLCache.

As of iOS 5, NSURLCache automatically saves responses to disk. I haven’t found anything in the release notes that confirms the change, but I tried it both with iOS 4.3/5.0/5.1 on the simulator and the device, and with every 5.x version, a disk cache file is created and populated. This is great, as many developers probably aren’t aware of this and the system just does the right thing on its own – and it’s fast:

If the Cache-Control headers indicate that this request can be cached, iOS automatically saves it to a local SQLite cache file in AppDirectory/Caches/(bundleid)/Cache.db. For example, public, max-age=31536000 marks that the request cache will be valid for a year, as max-age is listed in seconds.

The SQLite scheme for the cache looks identical to the one used in OS X:

So, why should you care? Well, the Cache.db caches any file that has a correct Cache-Control header set. Thus, if you download a PDF document, it might end up in your disk cache as well, taking up twice the memory.

The default NSURLCache will be used, with a disk limit of 20MB. You can easily test this with GDB/LLDB:

1
p (int)[[NSURLCache sharedURLCache] diskCapacity]

The memoryCapacity defaults to 40MB, although the cache will clear itself in low-memory situations.

So for downloads that you manually save to disk, you might want to override the NSURLConnection delegate connection:willCacheResponse: and return nil:

When creating NSURLRequest using requestWithURL:cachePolicy:timeoutInterval:, you can define the cachePolicy, but this only allows you to choose if and how the cache will be read.

Available options are: only use the cache value (NSURLRequestReturnCacheDataDontLoad); try the cache and load if different (NSURLRequestReturnCacheDataElseLoad); or ignore the cache entirely (NSURLRequestReloadIgnoringLocalCacheData).

The default option, if not set explicitly, is NSURLRequestUseProtocolCachePolicy, which most of the time is equivalent to NSURLRequestReturnCacheDataElseLoad. This uses the cache if the object hasn’t changed. There are a few other options in the enum, but those are unimplemented.

Note: There doesn’t seem a way to force caching of certain requests; connection:willCacheResponse: is only called if the response contains a Cache-Control header, according to Apple’s documentation:

The delegate receives connection:willCacheResponse: messages only for protocols that support caching.

Lastly, Apple suggests that caching can also be fine-tuned with subclassing NSURLProtocol, which indeed allows some interesting use cases, like providing a cache for UIWebView or decrypting files on the fly.

If you’re not yet using AFNetworking, you really should. It’s a big step forward compared to classical NSURLConnection handling, even if Apple recently added a few new fancy shorthands in iOS 5. In AFNetworking, your network operations are indeed subclasses of NSOperation, which allows much better control over what’s currently running, and AFHTTPClient is the perfect base class to implement any API.