惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

C
CERT Recently Published Vulnerability Notes
D
Docker
GbyAI
GbyAI
博客园 - 三生石上(FineUI控件)
博客园_首页
H
Help Net Security
T
The Exploit Database - CXSecurity.com
Microsoft Security Blog
Microsoft Security Blog
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
P
Privacy & Cybersecurity Law Blog
AWS News Blog
AWS News Blog
Spread Privacy
Spread Privacy
F
Full Disclosure
Blog — PlanetScale
Blog — PlanetScale
Y
Y Combinator Blog
V
Vulnerabilities – Threatpost
P
Proofpoint News Feed
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
aimingoo的专栏
aimingoo的专栏
Simon Willison's Weblog
Simon Willison's Weblog
MyScale Blog
MyScale Blog
P
Palo Alto Networks Blog
S
Security Affairs
T
Tailwind CSS Blog
T
Tor Project blog
W
WeLiveSecurity
G
GRAHAM CLULEY
Know Your Adversary
Know Your Adversary
The Hacker News
The Hacker News
腾讯CDC
M
MIT News - Artificial intelligence
D
DataBreaches.Net
量子位
Martin Fowler
Martin Fowler
T
Threat Research - Cisco Blogs
博客园 - 【当耐特】
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
云风的 BLOG
云风的 BLOG
C
Cisco Blogs
I
InfoQ
Engineering at Meta
Engineering at Meta
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
C
CXSECURITY Database RSS Feed - CXSecurity.com
O
OpenAI News
美团技术团队
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
T
Troy Hunt's Blog
Microsoft Azure Blog
Microsoft Azure Blog
L
LangChain Blog
A
About on SuperTechFans

Peter Steinberger

OpenClaw, OpenAI and the future | Peter Steinberger Shipping at Inference-Speed | Peter Steinberger The Signature Flicker | Peter Steinberger Just Talk To It - the no-bs Way of Agentic Engineering | Peter Steinberger Claude Code Anonymous | Peter Steinberger Live Coding Session: Building Arena | Peter Steinberger My Current AI Dev Workflow | Peter Steinberger Essential Reading for Agentic Engineers - August 2025 | Peter Steinberger Just One More Prompt | Peter Steinberger Poltergeist: The Ghost That Keeps Your Builds Fresh | Peter Steinberger Don't read this Startup Slop | Peter Steinberger Essential Reading for Agentic Engineers - July 2025 | Peter Steinberger Self-Hosting AI Models After Claude's Usage Limits | Peter Steinberger Logging Privacy Shenanigans | Peter Steinberger VibeTunnel's first AI-anniversary | Peter Steinberger Making AppleScript Work in macOS CLI Tools: The Undocumented Parts | Peter Steinberger Peekaboo 2.0 – Free the CLI from its MCP shackles | Peter Steinberger Command your Claude Code Army, Reloaded | Peter Steinberger Essential Reading for Agentic Engineers | Peter Steinberger Slot Machines for Programmers: How Peter Builds Apps 20x Faster with AI | Peter Steinberger My AI Workflow for Understanding Any Codebase | Peter Steinberger stats.store: Privacy-First Sparkle Analytics | Peter Steinberger Showing Settings from macOS Menu Bar Items: A 5-Hour Journey | Peter Steinberger VibeTunnel: Turn Any Browser into Your Mac's Terminal | Peter Steinberger Vibe Meter 2.0: Calculating Claude Code Usage with Token Counting | Peter Steinberger llm.codes: Make Apple Docs AI-Readable | Peter Steinberger Automatic Observation Tracking in UIKit and AppKit: The Feature Apple Forgot to Mention | Peter Steinberger Peekaboo MCP – lightning-fast macOS screenshots for AI agents | Peter Steinberger Migrating 700+ Tests to Swift Testing: A Real-World Experience | Peter Steinberger Commanding Your Claude Code Army | Peter Steinberger Code Signing and Notarization: Sparkle and Tears | Peter Steinberger Vibe Meter: Monitor Your AI Costs | Peter Steinberger Claude Code is My Computer | Peter Steinberger Stop Over-thinking AI Subscriptions | Peter Steinberger Introducing Demark: HTML in. MD out. Blink-fast. | Peter Steinberger The Future of Vibe Coding: Building with AI, Live and Unfiltered | Peter Steinberger MCP Best Practices | Peter Steinberger Finding My Spark Again | Peter Steinberger Top-Level Menu Visibility in SwiftUI for macOS | Peter Steinberger Fixing keyboardShortcut in SwiftUI | Peter Steinberger Supporting Both Tap and Long Press on a Button in SwiftUI | Peter Steinberger On Using Apple Silicon Mac Mini for Continuous Integration | Peter Steinberger Apple Silicon M1: A Developer's Perspective | Peter Steinberger Gardening Your Twitter: Curating Your Timeline | Peter Steinberger Gardening Your Twitter: Growing Your Followers | Peter Steinberger Forbidden Controls in Catalyst: Optimize Interface for Mac | Peter Steinberger Disabling Keyboard Avoidance in SwiftUI's UIHostingController | Peter Steinberger The State of SwiftUI | Peter Steinberger Logging in Swift | Peter Steinberger Building with Swift Trunk Development Snapshots | Peter Steinberger Calling Super at Runtime in Swift | Peter Steinberger zld — A Faster Version of Apple's Linker | Peter Steinberger How to Fix LLDB: Couldn't IRGen Expression | Peter Steinberger Updating macOS on a Hackintosh | Peter Steinberger InterposeKit — Elegant Swizzling in Swift | Peter Steinberger The Great Mac Catalyst Text Input Crash Hunt | Peter Steinberger Jailbreaking for iOS Developers | Peter Steinberger Network Kernel Core Dump | Peter Steinberger How to macOS Core Dump | Peter Steinberger Kernel Panics and Surprise boot-args | Peter Steinberger The LG UltraFine 5K, kernel_task, and Me | Peter Steinberger Let's Try This Again | Peter Steinberger How We Work at PSPDFKit | Peter Steinberger Swizzling in Swift | Peter Steinberger WWDC for First-Timers, 2019 Edition | Peter Steinberger Challenges of Adopting Drag and Drop | Peter Steinberger Marzipan: Porting iOS Apps to the Mac | Peter Steinberger How to Use Slack and Not Go Crazy | Peter Steinberger Hardcore Debugging - Heavy Weapons for Hard Bugs | Peter Steinberger Binary Frameworks in Swift | Peter Steinberger Even Swiftier Objective-C | Peter Steinberger The Case for Deprecating UITableView | Peter Steinberger Running tests with Clang Address Sanitizer | Peter Steinberger UI testing on iOS, without busy waiting | Peter Steinberger Hiring a distributed team | Peter Steinberger Writing Good Bug Reports | Peter Steinberger Real-time collaboration, Apple, and you | Peter Steinberger Converting Xcode Test Runs to JUnit, the Fast Way | Peter Steinberger Efficient iOS Version Checking | Peter Steinberger Investigating Thread Safety of UIImage | Peter Steinberger Swifty Objective-C | Peter Steinberger Running UI Tests on iOS With Ludicrous Speed | Peter Steinberger A Pragmatic Approach to Cross-Platform | Peter Steinberger Surprises with Swift Extensions | Peter Steinberger Using ccache for Fun and Profit | Peter Steinberger UITableViewController designated initializer woes | Peter Steinberger Researching ResearchKit | Peter Steinberger The curious case of rotation with multiple windows on iOS 8 | Peter Steinberger UIKit Debug Mode | Peter Steinberger Retrofitting containsString: on iOS 7 | Peter Steinberger Hacking with Aspects | Peter Steinberger Fixing UITextView On iOS 7 | Peter Steinberger Fixing What Apple Doesn't | Peter Steinberger How To Inspect The View Hierarchy Of Third-Party Apps | Peter Steinberger Fixing UISearchDisplayController On iOS 7 | Peter Steinberger Smart Proxy Delegation | Peter Steinberger Adding Keyboard Shortcuts To UIAlertView | Peter Steinberger How To Center Content Within UIScrollView | Peter Steinberger UIAppearance for Custom Views | Peter Steinberger Hacking Block Support Into UIMenuItem | Peter Steinberger
A Story About Swizzling "the Right Way™" and Touch Forwarding | Peter Steinberger
Peter Steinberger · 2014-07-04 · via Peter Steinberger

Some people think of me as the guy that does crazy things to ObjC and swizzles everything. Not true. In PSPDFKit I’m actually quite conservative, but I do enjoy spending time with the runtime working on things such as Aspects - a library for aspect oriented programming.

After my initial excitement, things have stalled a bit. I shipped Aspects in our PDF framework, and people started complaining that it sometimes freezes the app, basically looping deep within the runtime, when the New Relic SDK was also linked.

Of course I tried to fix this. Contacting New Relic didn’t bring any results at first, even after two paying customers started to report the same issue. After periodically bugging them for over a month I finally got a non-canned response, pointing me to a blog entry about method swizzling.

This basically says that using method_exchangeImplementations is really bad, and that pretty much everybody does swizzling wrong. And they indeed have a point. Regular swizzling messes not only with your brain but also with assumptions that the runtime makes. Suddenly _cmd no longer is what it is supposed to be, and while in most cases it does not matter, there are a few cases where it does very much.

How most people swizzle (including me)

This is the swizzling helper that I’ve used during the last few years:

BOOL PSPDFReplaceMethodWithBlock(Class c, SEL origSEL, SEL newSEL, id block) {
    PSPDFAssert(c && origSEL && newSEL && block);
    if ([c respondsToSelector:newSEL]) return YES; // Selector already implemented, skip

    Method origMethod = class_getInstanceMethod(c, origSEL);

    // Add the new method.
    IMP impl = imp_implementationWithBlock(block);
    if (!class_addMethod(c, newSEL, impl, method_getTypeEncoding(origMethod))) {
        PSPDFLogError(@"Failed to add method: %@ on %@", NSStringFromSelector(newSEL), c);
        return NO;
    }else {
        Method newMethod = class_getInstanceMethod(c, newSEL);

        // If original doesn't implement the method we want to swizzle, create it.
        if (class_addMethod(c, origSEL, method_getImplementation(newMethod), method_getTypeEncoding(origMethod))) {
            class_replaceMethod(c, newSEL, method_getImplementation(origMethod), method_getTypeEncoding(newMethod));
        }else {
            method_exchangeImplementations(origMethod, newMethod);
        }
    }
    return YES;
}

This is a very common approach, with a small twist that it takes a block and uses imp_implementationWithBlock to create an IMP trampoline out of it. Usage is as follows:

SEL touchesMovedSEL = NSSelectorFromString(@"pspdf_wacomTouchesMoved:withEvent:");
PSPDFWacomSwizzleMethodWithBlock(viewClass, @selector(touchesMoved:withEvent:), touchesMovedSEL, ^(UIView *_self, NSSet *touches, UIEvent *event) {
    [WacomManager.getManager.currentlyTrackedTouches moveTouches:touches knownTouches:[event touchesForView:_self] view:_self];
    ((void ( *)(id, SEL, NSSet *, UIEvent *))objc_msgSend)(_self, touchesMovedSEL, touches, event); // call the original method
});

(Yes, Wacom’s framework for stylus support is horrible. There are way better ways to hook into touch handling, such as subclassing UIApplication’s sendEvent:.)

Note the cast to objc_msgSend. While this (by luck) worked without casting in the earlier days, this will probably crash your arm64 build if you don’t cast this correctly, because the variable argument casting specifications changed. Add #define OBJC_OLD_DISPATCH_PROTOTYPES 0 to your files to make sure this is detected at compile time, or even better, use Xcode 6 and enable error checking on this:

The Crash

This works as expected in most cases, but has the issue that the original implementation will be called with a different _cmd than it expects. This can be a problem when _cmd is actually used, such as in the touch forwarding logic. I learned this the hard way after swizzling touchesMoved:withEvent: to inject additional logic. The app crashed with the popular doesNotRecognizeSelector: exception.

* thread #1: tid = 0x695bfa, 0x0000000104cee973 libobjc.A.dylib`objc_exception_throw, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
    frame #0: 0x0000000104cee973 libobjc.A.dylib`objc_exception_throw
    frame #1: 0x000000010507e65d CoreFoundation`-[NSObject(NSObject) doesNotRecognizeSelector:] + 205
    frame #2: 0x0000000104fded8d CoreFoundation`___forwarding___ + 973
    frame #3: 0x0000000104fde938 CoreFoundation`__forwarding_prep_0___ + 120
    frame #4: 0x0000000103080ae1 UIKit`forwardTouchMethod + 247
  * frame #5: 0x00000001000f071e PSPDFCatalog`__56-[PSPDFWacomStylusDriver prepareViewForTouchMonitoring:]_block_invoke_2(.block_descriptor=0x0000000115e1d010, _self=0x000000010e94a2a0, touches=0x000000010fefbd40, event=0x000000010e61fd50) + 382 at PSPDFWacomStylusDriver.m:122
    frame #6: 0x0000000102f85bbc UIKit`-[UIWindow _sendTouchesForEvent:] + 372
    frame #7: 0x0000000102f866e4 UIKit`-[UIWindow sendEvent:] + 925
    frame #8: 0x0000000102f5e29a UIKit`-[UIApplication sendEvent:] + 211
    frame #9: 0x0000000102f4baed UIKit`_UIApplicationHandleEventQueue + 9579
    frame #10: 0x0000000104f7cd21 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
    ...
    frame #15: 0x0000000102f4de33 UIKit`UIApplicationMain + 1010

Somehow UIKit wants to call pspdf_wacomTouchesMoved:withEvent: on a class that I definitely did not swizzle, and so of course the runtime throws an exception. But how did we end up here? Investigating the stack trace, UIKit’s forwardTouchMethod looks interesting. Let’s see what this actually does.

Touch forwarding in UIKit

The base class for UIView is UIResponder, and it implements all basic touch handling: (Note: I don’t have access to the UIKit sources, so this might not be 100% accurate. The snippets are based on disassembling UIKit and manually converting this back to C.)

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    forwardTouchMethod(self, _cmd, touches, event);
}

Here it gets interesting. _cmd is used directly in this C function that (at least the name suggests) then forwards our touches up the responder chain. But let’s keep digging, just to make sure. For curiosity’s sake, I translated the whole function, including legacy behavior. (I don’t remember any announcement where Apple changed this in iOS 5. Is this somewhere documented? Hit me up on Twitter if you know more.)

static void forwardTouchMethod(id self, SEL _cmd, NSSet *touches, UIEvent *event) {
	// The responder chain is used to figure out where to send the next touch
    UIResponder *nextResponder = [self nextResponder];
    if (nextResponder && nextResponder != self) {

    	// Not all touches are forwarded - so we filter here.
        NSMutableSet *filteredTouches = [NSMutableSet set];
        [touches enumerateObjectsUsingBlock:^(UITouch *touch, BOOL *stop) {

        	// Checks every touch for forwarding requirements.
            if ([touch _wantsForwardingFromResponder:self toNextResponder:nextResponder withEvent:event]) {
                [filteredTouches addObject:touch];
            }else {
            	// This is interesting legacy behavior. Before iOS 5, all touches are forwarded (and this is logged)
                if (!_UIApplicationLinkedOnOrAfter(12)) {
                    [filteredTouches addObject:touch];

                    // Log old behavior
                    static BOOL didLog = 0;
                    if (!didLog) {
                        NSLog(@"Pre-iOS 5.0 touch delivery method forwarding relied upon. Forwarding -%@ to %@.", NSStringFromSelector(_cmd), nextResponder);
                    }
                }
            }
        }];

        // here we basically call [nextResponder touchesBegan:filteredTouches event:event];
        [nextResponder performSelector:_cmd withObject:filteredTouches withObject:event];
    }
}

At this point I was a few hours in, digging through Apple’s touch forwarding code. You can use Hopper to read through _wantsForwardingFromResponder:toNextResponder:withEvent:. Most of the code seems to track forwarding phases, checks for exclusiveTouch, different windows and there’s even a dedicated _UITouchForwardingRecipient class involved. There’s quite a lot more logic in UITouch than I would have expected.

Forwarding using _cmd is not restricted to touch handling at all - on the Mac it’s used for mouse[Entered|Exited|Moved]: as well.

A different approach on swizzling

Our naive use of method_exchangeImplementations() broke the _cmd assumption and resulted in a crash. How can we fix this? New Relic suggested using the direct method override. Let’s try that:

__block IMP originalIMP = PSPDFReplaceMethodWithBlock(viewClass, @selector(touchesMoved:withEvent:), ^(UIView *_self, NSSet *touches, UIEvent *event) {
    [WacomManager.getManager.currentlyTrackedTouches moveTouches:touches knownTouches:[event touchesForView:_self] view:_self];
     ((void ( *)(id, SEL, NSSet *, UIEvent *))originalIMP)(_self, @selector(touchesMoved:withEvent:), touches, event);
});

static IMP PSPDFReplaceMethodWithBlock(Class c, SEL origSEL, id block) {
    NSCParameterAssert(block);

    // get original method
    Method origMethod = class_getInstanceMethod(c, origSEL);
    NSCParameterAssert(origMethod);

    // convert block to IMP trampoline and replace method implementation
    IMP newIMP = imp_implementationWithBlock(block);

    // Try adding the method if not yet in the current class
    if (!class_addMethod(c, origSEL, newIMP, method_getTypeEncoding(origMethod))) {
        return method_setImplementation(origMethod, newIMP);
    }else {
        return method_getImplementation(origMethod);
    }
}

This solves our problem. We preserve the correct selector (there’s no pspdf_wacomTouchesMoved:withEvent: method anymore) and thus UIKit’s touch forwarding works as expected. The method replacing logic is also simpler.

However, there are downsides to this approach as well. We are now modifying the touchesBegan:withEvent: method of our custom UIView subclass. There is no default implementation yet, so we get the IMP from UIResponder and then manually call this. Imagine if at some later point, somebody else would swizzle touchesBegan:withEvent: on UIView directly using the same technique. Assuming UIView has no custom touch handling code, they would get the IMP from UIResponder and add a new method to UIView. But then our method gets called, which already captured the IMP of UIResponder and completely ignores the fact that we modified UIView as well.

Epilogue

There are solutions to this problem, but they are extremely complex, such as CydiaSubstrate’s MSHookMessageEx, but since this requires a kernel patch (and thus a jailbreak), it’s not something you would use in an App Store app.

If you read trough the whole article and are wondering why I’m not simply subclassing the touch handlers, you are right. This is the usual approach. However we recently added stylus support for a variety of vendors, and this is built via external driver classes, so that we don’t have to “pollute” the framwork with the different approaches. Wacom is the only vendor that requires direct touch forwarding, and every vendor has it’s own way to manage touches and views. Integrating all these into a single class would result in a very hard-to-maintain class, and licensing issues would also prevent us from shipping the framework binaries directly. Furthermore, only some companies use the stylus code, so we designed this to be modular. (e.g. Dropbox just uses PSPDFKit as a Viewer, and thus doesn’t need that part.)

Further Reading: