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

推荐订阅源

S
Security Affairs
S
Schneier on Security
T
Tenable Blog
G
GRAHAM CLULEY
Latest news
Latest news
D
Darknet – Hacking Tools, Hacker News & Cyber Security
A
Arctic Wolf
I
Intezer
Cyberwarzone
Cyberwarzone
T
The Exploit Database - CXSecurity.com
T
Tailwind CSS Blog
K
Kaspersky official blog
Blog — PlanetScale
Blog — PlanetScale
C
Cyber Attacks, Cyber Crime and Cyber Security
T
Threat Research - Cisco Blogs
爱范儿
爱范儿
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
博客园 - 叶小钗
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Recent Commits to openclaw:main
Recent Commits to openclaw:main
P
Palo Alto Networks Blog
WordPress大学
WordPress大学
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
博客园 - 司徒正美
The Cloudflare Blog
Help Net Security
Help Net Security
罗磊的独立博客
博客园 - 聂微东
Jina AI
Jina AI
Project Zero
Project Zero
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
L
LINUX DO - 最新话题
V
V2EX
人人都是产品经理
人人都是产品经理
美团技术团队
博客园 - 【当耐特】
Spread Privacy
Spread Privacy
J
Java Code Geeks
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Security Latest
Security Latest
The Last Watchdog
The Last Watchdog
Stack Overflow Blog
Stack Overflow Blog
雷峰网
雷峰网
S
Securelist
Forbes - Security
Forbes - Security
博客园 - 三生石上(FineUI控件)
Microsoft Azure Blog
Microsoft Azure Blog
P
Privacy International News Feed
宝玉的分享
宝玉的分享
C
CERT Recently Published Vulnerability Notes

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 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 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
Even Swiftier Objective-C | Peter Steinberger
Peter Steinberger · 2017-06-14 · via Peter Steinberger

Another fascinating WWDC is behind us. This year we again witnessed a whole host of newly presented features and refinements to the Swift language, now already in its fourth installment. It’s great to see how the language has progressed over the last couple years. Unfortunately, since binary compatibility has been delayed further (at least there’s a Manifesto now), and module stability is still even further away, we are unable to use Swift in our binary PSPDFKit SDK. However, we do use it for our tests and in PDF Viewer.

If you write a new app in 2017, you should use Swift. Though there may still be cases where you need to work with Objective-C. Perhaps you are dealing with a legacy application, or you might have other reasons why Swift cannot be used. For example, if your app heavily inter-ops with C++ (like ours does). In that case, you might have been disappointed on the relatively little attention that the other main Apple programming language received recently. But fear not, PSPDFKit is coming to the rescue. Here are some of our favorite tips on how to improve Objective-C on your own.

If you haven’t read our part 1 “Swifty Objective-C”, we’d recommend you to check it out first, as this article builds on some of the concepts outlined there. It’s also a treasure trove of awesome little things that will make your code better, safer and more compact. Oh, and speaking of better code. Save your precious time and let a computer do code formatting for you!

var and let

In our original “Swifty Objective-C” blog post we talked about the C++ auto keyword and how it’s great for preserving type information while also making the code more readable and easier to write. This is especially true when dealing with generics or block types. Since then, Objective-C learned the same trick via the new __auto_type keyword. Since nobody wants to type __auto_type all day long, we decided to make things nicer by defining Swift-like macros for it.

#if defined(__cplusplus)
#define let auto const
#else
#define let const __auto_type
#endif

#if defined(__cplusplus)
#define var auto
#else
#define var __auto_type
#endif

Those even work if you mix Objective-C and C++ code.

foreach

Both Swift and Objective-C have a for..in syntax. The benefit of Swift is that the loop variable type is inferred from the collection. This wasn’t possible for a long time in Objective-C as the language didn’t have generics. Now, we finally have lightweight generics in Objective-C however the underlying code hasn’t been fully updated. NSFastEnumeration has a specialization called NSEnumerator that has been generic-ified, but collection classes don’t inherit from that yet.

We tried to come up with a trick to infer the object type from any collection, but not all of them have helpers like firstObject that can be used to infer the item type. However, this is where categories are super useful and allow us to add this ourselves! Shout-out to Martin Kiss who works on the amazing PaintCode for coming up with the idea and sharing it with the world.

@protocol PSPDFFastEnumeration <NSFastEnumeration>
- (id)pspdf_enumeratedType;
@end

// Usage: foreach (s, strings) { ... }
#define foreach(element, collection) for (typeof((collection).pspdf_enumeratedType) element in (collection))

@interface NSArray <ElementType> (PSPDFFastEnumeration) <PSPDFFastEnumeration>
- (ElementType)pspdf_enumeratedType;
@end

@interface NSSet <ElementType> (PSPDFFastEnumeration) <PSPDFFastEnumeration>
- (ElementType)pspdf_enumeratedType;
@end

@interface NSDictionary <KeyType, ValueType> (PSPDFFastEnumeration) <PSPDFFastEnumeration>
- (KeyType)pspdf_enumeratedType;
@end

This is just a snippet. Get the full code listing from our gist.

Note: This is pure syntactic sugar - this category doesn’t emit any code, yet it enables us to rewrite our for loops like this:

let annotations = [document annotationsForPageAtIndex:pageView.pageIndex type:PSPDFAnnotationTypeLink];

// old
for (PSPDFAnnotation *annotation in annotations) {
    NSLog(@"Color of %@ is %@", annotation, annotation.color);
}

// new
foreach (annotation, annotations) {
    NSLog(@"Color of %@ is %@", annotation, annotation.color);
}

Now, you might wonder, why is this any better? What’s wrong with having the type visible? Not much, really. However, this is still preferable, as it adds compile time safety to your code. foreach will only work with a correctly defined generic array and it will fail if the array contains anything else that’s not of type PSPDFAnnotation. This is incredibly useful if you have a large codebase, where a refactor might cause ripples through your codebase, leading you to forget to update one part.

How bad is it? Imagine if we change the code to this:

for (NSString *annotationName in annotations) {
    NSLog(@"Annotation name is %@", annotationName.uppercaseString);
}

This will crash at runtime. It doesn’t even cause a compiler warning, despite generics! This is definitely something the compiler can learn, but until then using foreach will protect you just as well.

Update: Joe Groff mentioned that with Objective-C++ files, the C++ syntax for (auto annotation : annotations) also works for NSFastEnumeration-Objective-C collections.

Type information for copy/mutableCopy

Objective-C eventually got instancetype to improve casting and type forwarding, however that never reached copy (or its mutable counterpart). This is an easy cause for bugs. Imagine the following snippet:

+ (NSOrderedSet<NSString *> *)propertyKeys {
    NSMutableSet<NSString *> *propertyKeys = super.propertyKeys.mutableCopy;
    let allObjects = propertyKeys.allObjects; // BOOM runtime crash

This code compiles without warning but will crash at runtime with a “selector not found for allObjects” type of crash. This actually happened in PSPDFKit’s codebase when we did a large-scale refactor and forgot to update the copy.

There’s only one copy selector, and it’s not generic. It’s also not a case where returning instancetype would be the right fix, as there’s no general way to understand what a mutable counterpart of a class pair is called and if one even exists.

@interface NSObject <NSObject>
- (id)copy;
- (id)mutableCopy;
@end

However, we can just add that ourselves! Again, a header-only declaration that simply redefines copy on our collections. This won’t magically add types to every single object, but it will solve the common case of (mutable) copying collections. The downside is that - as we redeclare a selector - id-casted objects will create a warning when copy is called on them. This happens because the compiler now sees multiple “choices” when there really aren’t, but the compiler doesn’t know that.

@interface NSArray <ElementType> (PSPDFSafeCopy)

/// Same as `copy` but retains the generic type.
- (NSArray <ElementType> *)copy;

/// Same as `mutableCopy` but retains the generic type.
- (NSMutableArray <ElementType> *)mutableCopy;

@end

This is just a snippet. Get the full code listing from our gist.

With that, we can rewrite our previous code example:

+ (NSOrderedSet<NSString *> *)propertyKeys {
    let propertyKeys = super.propertyKeys.mutableCopy;
    let allObjects = propertyKeys.allObjects; // COMPILE TIME ERROR

And suddenly we converted a runtime crash into a compile-time error. Win! As we adopted this in PSPDFKit, we found multiple places where we did wrong casts and where things worked because we only iterated over a collection. These were crashes waiting to happen. Note that this now also allows us to use let, which is nice and helps readability.

defer

Swift has defer to schedule blocks of work to clean up when you leave the current scope. This is great for cleanup when you can exit/throw in multiple places, and both simplifies code and helps to reduce leaks.

CGImageSourceRef imageSource = CGImageSourceCreateWithURL((CFURLRef)fileURL, NULL);
if (!imageSource) {
    // set error
    return NO;
}

CGImageRef image = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, NULL);
if (!image) {
    // set error
    return NO;
}

thumbnail = [UIImage imageWithCGImage:image scale:scale orientation:UIImageOrientationUp];
CFRelease(imageSource);
CFRelease(image);
return YES;

Notice the leak? If creating the image fails, we leak imageSource as we exit early. Of course, that’s fixable when we special-handle the first exit condition, but that’s hard and easy to get wrong. A better solution: Use defer to declare what needs to happen when things go out of scope. This is conceptually similar to C++ RAII, and a similar solution can be achieved with custom smart pointers in that case, however defer is much more flexible and useful for many issues.

Better:

CGImageSourceRef imageSource = CGImageSourceCreateWithURL((CFURLRef)fileURL, NULL);
if (!imageSource) {
    // set error
    return NO;
}
pspdf_defer { CFRelease(imageSource); };

CGImageRef image = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, NULL);
if (!image) {
    // set error
    return NO;
}
pspdf_defer { CFRelease(image); };

thumbnail = [UIImage imageWithCGImage:image scale:scale orientation:UIImageOrientationUp];
return YES;

So how does this work? We define a block and add __attribute__((cleanup)) to it that tells the compiler to execute the function defined in that attribute. We pass along the block as a parameter and thus execute the block when the scope is exited. This attribute is a GCC extension that was adopted by Clang.

// Similar to defer in Swift
#define pspdf_defer_block_name_with_prefix(prefix, suffix) prefix ## suffix
#define pspdf_defer_block_name(suffix) pspdf_defer_block_name_with_prefix(pspdf_defer_, suffix)
#define pspdf_defer __strong void(^pspdf_defer_block_name(__LINE__))(void) __attribute__((cleanup(pspdf_defer_cleanup_block), unused)) = ^
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-function"
static void pspdf_defer_cleanup_block(__strong void(^*block)(void)) {
    (*block)();
}
#pragma clang diagnostic pop

Part of the magic here is to use the current line number (__LINE__) as part of the local variable. This enables us to use the macro multiple times inside the same scope.

I’ve first seen this in libextobjc from Justin Spahr-Summers. While this is trivial to add via a macro, first-class language support would make things much better. Please dupe rdar://32485852 if you agree.

Checked KeyPaths

When working with Apple’s APIs, you’ll undoubtedly find a bunch of cases where you’ll need to pass string keypaths as parameters. The most common case is KVO / KVC, but also AVFoundation and other APIs. Those so called stringly typed APIs are inherently unsafe, as we get no compiler checks about the validity of the passed-in paths. Swift 3 introduced the #keyPath keyword, which makes things much safer over in Swift land and Swift 4 further builds on that with its Smart KeyPaths. Objective-C, however is still lacking in this regard. Fortunately, we can again use macros to make things a bit better.

#if DEBUG
#define PSPDF_KEYPATH(object, property) ((void)(NO && ((void)object.property, NO)), @ #property)
#else
#define PSPDF_KEYPATH(object, property) @ #property
#endif

And here is how you would use it:

[player addObserver:self forKeyPath:PSPDF_KEYPATH(player, rate) options:NSKeyValueObservingOptionNew context:&PSPDFMediaPlayerKVOToken];

The macro resolves to a simple string for production builds, so it’s just as fast as using strings directly. For debug builds you will in addition get compiler-level checks, so if the rate property ever changes, you’ll notice that immediately due to a compiler error.

Boxing CGRect, CGPoint & co

A while ago, Objective-C got literals and a shorthand for boxing. This works for integers, enums and for any struct that declares the __attribute__((objc_boxable)), this was all built and there are even tests in Clang that test this. However, the declarations in CoreGraphics have never been updated. rdar://32486932

BUT. It’s easy to add this yourself, as Rob Mayoff pointed out:

typedef struct __attribute__((objc_boxable)) CGPoint CGPoint;
typedef struct __attribute__((objc_boxable)) CGSize CGSize;
typedef struct __attribute__((objc_boxable)) CGRect CGRect;
typedef struct __attribute__((objc_boxable)) CGVector CGVector;
typedef struct __attribute__((objc_boxable)) CGAffineTransform CGAffineTransform;
typedef struct __attribute__((objc_boxable)) UIEdgeInsets UIEdgeInsets;
typedef struct __attribute__((objc_boxable)) _NSRange NSRange;

With that, @() does exactly what you expect and we can retire our BOXED() macro.

CGRect rect = CGRectMake(0, 0, 100, 50);
NSValue *boxedRect = @(rect);
NSLog(@"boxed: %@", boxedRect);