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

推荐订阅源

H
Help Net Security
The GitHub Blog
The GitHub Blog
F
Fortinet All Blogs
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Simon Willison's Weblog
Simon Willison's Weblog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Cisco Talos Blog
Cisco Talos Blog
P
Privacy & Cybersecurity Law Blog
I
Intezer
Y
Y Combinator Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
N
Netflix TechBlog - Medium
The Hacker News
The Hacker News
AWS News Blog
AWS News Blog
aimingoo的专栏
aimingoo的专栏
A
About on SuperTechFans
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Stack Overflow Blog
Stack Overflow Blog
Hacker News: Ask HN
Hacker News: Ask HN
酷 壳 – CoolShell
酷 壳 – CoolShell
量子位
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
B
Blog
T
Tor Project blog
C
Cybersecurity and Infrastructure Security Agency CISA
云风的 BLOG
云风的 BLOG
博客园_首页
V2EX - 技术
V2EX - 技术
T
Threat Research - Cisco Blogs
腾讯CDC
宝玉的分享
宝玉的分享
博客园 - 叶小钗
罗磊的独立博客
S
Securelist
The Last Watchdog
The Last Watchdog
Google Online Security Blog
Google Online Security Blog
Scott Helme
Scott Helme
博客园 - 司徒正美
W
WeLiveSecurity
有赞技术团队
有赞技术团队
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
S
Secure Thoughts
NISL@THU
NISL@THU
N
News and Events Feed by Topic
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
雷峰网
雷峰网
大猫的无限游戏
大猫的无限游戏
K
Kaspersky official blog
IT之家
IT之家

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 A Story About Swizzling "the Right Way™" and Touch Forwarding | 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 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
Smart Proxy Delegation | Peter Steinberger
Peter Steinberger · 2013-07-31 · via Peter Steinberger

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

    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:

    [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:

- (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:

@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:

- (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:

_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:

    _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:


- (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.