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

推荐订阅源

Engineering at Meta
Engineering at Meta
人人都是产品经理
人人都是产品经理
大猫的无限游戏
大猫的无限游戏
博客园 - 三生石上(FineUI控件)
量子位
腾讯CDC
The Cloudflare Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
云风的 BLOG
云风的 BLOG
Vercel News
Vercel News
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
L
LangChain Blog
aimingoo的专栏
aimingoo的专栏
The Hacker News
The Hacker News
T
The Exploit Database - CXSecurity.com
B
Blog
S
SegmentFault 最新的问题
P
Privacy & Cybersecurity Law Blog
T
Threatpost
博客园 - 聂微东
T
Tailwind CSS Blog
The Last Watchdog
The Last Watchdog
C
Check Point Blog
N
Netflix TechBlog - Medium
D
DataBreaches.Net
爱范儿
爱范儿
IT之家
IT之家
S
Secure Thoughts
M
MIT News - Artificial intelligence
NISL@THU
NISL@THU
C
Cisco Blogs
TaoSecurity Blog
TaoSecurity Blog
有赞技术团队
有赞技术团队
A
Arctic Wolf
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
P
Proofpoint News Feed
Spread Privacy
Spread Privacy
Schneier on Security
Schneier on Security
Simon Willison's Weblog
Simon Willison's Weblog
G
GRAHAM CLULEY
雷峰网
雷峰网
Project Zero
Project Zero
博客园 - Franky
H
Heimdal Security Blog
A
About on SuperTechFans
Security Latest
Security Latest
Webroot Blog
Webroot Blog
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Hugging Face - Blog
Hugging Face - Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More

Inside Nutrient

A guide to the invisible work behind documents Introducing Nutrient Documents for Salesforce: Native document generation and signing Document AI vs. traditional OCR: Choosing between OCR, AI, and hybrid pipelines PDF SDK compliance and security evaluation checklist for enterprise teams (2026) Invariant Corp replaces paper processes with Nutrient Workflow and scales without limits What is process mapping? A complete guide Nutrient vs. Conga Composer for Salesforce document generation (2026) Document routing: How to automate document distribution The CTO’s AI playbook: Why accountability architecture beats orchestration Compliance workflow automation: Why built-in compliance is table stakes Workflow diagrams: Examples, symbols, and how to build one that actually runs Digital forms: Replace paper forms with automated workflows Approval workflow software: How to automate approvals Why document-centric automation is different The CEO’s AI playbook: Why decision architecture beats model selection Nutrient SDK product updates for Q1 2026 PDF redaction verification: How to prove sensitive data is permanently removed What is a VPAT? The complete guide to accessibility conformance reports What is PDF/UA? The accessible PDF standard explained Salesforce eSignatures: Generate, sign, and track documents in one flow Online document viewer: Options, tradeoffs, and how to embed one Document viewer for web apps: React, Vue, Angular (2026) Best document viewers in 2026: A buyer’s guide How to edit a PDF in Python: Add text, images, and annotations Nutrient advances Workflow platform with agentic AI for enterprise-grade speed and consistency in document-heavy operations How to create a Salesforce quote template from opportunity data The business case for accessibility: Five ways it drives enterprise value Python PDF library comparison (2026): 7 libraries for developers Why your AI agent hallucinates PDF table data PDF.js limitations: When to upgrade to a commercial PDF SDK How Subject scaled 5× with Nutrient’s PDF SDK without rebuilding its document layer I replaced our sales training with an AI coach that runs in Slack — here’s what broke Redirecting to: https://securitybuzz.com/cybersecurity-news/why-enterprise-permissions-are-ais-most-dangerous-inheritance/ Nutrient .NET SDK vs. iText Core: Complete comparison for .NET developers DocuVieware: Support’s most frequently asked setup questions Introducing Nutrient Workflow How to convert PDF to Word in C# (.NET) When email and spreadsheets stop working: Work order approval workflows for field teams on the move Compliance with confidence: Why document-centric automation is the foundation of your mission Nutrient expands AI Assistant, automating multistep document workflows inside any application What is document generation? A developer’s guide to PDF generation Document Converter data flow and how real-time watermarks skip the queue PDF/UA compliance guide: Requirements, standards, and best practices Computers still can’t understand you How Athena Intelligence built AI agents for regulated enterprises with Nutrient’s document infrastructure How to convert HTML to PDF (2026): 4 methods from browser print to SDK How to build a document extraction pipeline with Nutrient Vision API OCR vs. intelligent document processing: Choosing the right document extraction engine Beyond OCR: How document intelligence eliminates manual processing in regulated industries Nutrient vs. IronPDF: Complete comparison for .NET developers Nutrient vs. Aspose.PDF: Complete comparison for .NET developers Redirecting to: https://fortune.com/2026/02/19/openclaw-who-is-peter-steinberger-openai-sam-altman-anthropic-moltbook/ Lufthansa Systems uses Nutrient to deliver reliable, scalable PDF rendering for pilots worldwide Nutrient vs. Syncfusion: Complete comparison for .NET developers React’s useTransition: The hook you’re probably using wrong First City Monument Bank streamlines banking processes with Nutrient Workflow Redirecting to: https://www.sdcexec.com/warehousing/automation/article/22957364/nutrient-workflow-automation-the-missing-link-in-supply-chain-efficiency The complete guide to digital signatures: PAdES, CAdES, and XAdES explained Nutrient Python SDK: Production-grade document processing for Python Introducing agentic document editing for web applications with AI Assistant Nutrient vs. QuestPDF: Complete comparison for .NET developers How we fixed the GdPicture license expiration (and what to do if you’re affected) Red team security testing with agentic AI The future of healthcare document automation Best healthcare workflow software compared Nutrient SDK product updates for Q4 2025 How Harvey scaled legal document workflows 50 percent MoM without rebuilding infrastructure HIPAA-compliant document management in hospitals How we optimized rendering performance while handling thousands of annotations in React — Part 2 Automated PII removal with Nutrient API Redirecting to: https://www.devopsdigest.com/2026-low-code-no-code-predictions Redirecting to: https://www.kmworld.com/Articles/Editorial/ViewPoints/Leaders-predict-AI-to-continue-permeating-all-aspects-of-KM-in-2026-172594.aspx What are deep agents and how do they solve complex problems? Whipping up document magic: Your easy-bake recipe for Vue and Nutrient Web SDK 🧁 What I’ve learned about product iteration planning while building SDKs Passwordless document signing: Three-layer security guide New zip folder functionality streamlines file management in Document Automation Server The keyboard shortcuts playbook: Taking control of keyboard events in Nutrient Web SDK From experienced engineer to AI beginner: My unexpected journey AI-assisted manual testing: Handling Safari’s PDF rendering and UI quirks How to keep a 20-year-old SDK up to date How we optimized rendering performance while handling thousands of annotations in React — Part 1 Nutrient announces new executive hires to accelerate next phase of growth High performance UI using web workers Automate document conversion at scale with Python and Nutrient DCS From curiosity to PLG (and AI): My journey to understanding product-led growth Prost to progress: One year as Nutrient Pigeon usage at Nutrient: Bridging native SDKs to Flutter Modernizing CI build servers: How to migrate from Chef to Ansible Unix man pages: AI-friendly documentation since 1971 Consistent hashing for even load distribution Best AI redaction APIs: Complete comparison guide for 2025 Why AI document redaction matters for modern security From coding to coordinating: How AI transformed my workflow What is intelligent document processing (IDP)? A complete guide Enterprise PDF SDKs: Best PSPDFKit (now Nutrient) alternatives Nutrient SDK product updates for Q3 2025 GdPicture support best practices Redacting sensitive data with Nutrient AI redaction API How AI is transforming the customer experience at Nutrient: From instant answers to intelligent support
NSCopying in a Swift world
Douglas Hill · 2024-07-09 · via Inside Nutrient

This story begins when a customer of our iOS PDF SDK reported that when they used a subclass of our Document class, taking a certain action in our user interface (UI) would crash with this message:

Fatal error: Use of unimplemented initializer ‘init(dataProviders:loadCheckpointIfAvailable:)’

The crash was easy for us to reproduce. When implementing init(dataProviders:loadCheckpointIfAvailable:) in the Document subclass, the crash didn’t occur, but we don’t want our customers to need to implement it themselves to avoid crashing.

We’re fortunate to have a codebase that’s been continuously maintained while serving users in production since 2011. Thanks to the fantastic interoperability between Swift and Objective-C, we’re able to write all our new code in Swift while keeping older Objective-C code around.

However, coming up with a solution for the reported crash required a dive into Swift and Objective-C compatibility and reevaluating old code to apply a more modern programming mindset. That’s what we’ll explore in this post.

The designated initialiser pattern

To understand what’s going on, it’s necessary to cover some theory about how object initialisation works with class inheritance. Both Swift and Objective-C follow a few rules that make up the designated initialiser pattern. These rules are similar between the two languages, though they’re more strongly enforced by the compiler in Swift.

Consider two classes, a base class and a subclass. In other words:

class BaseClass {

}

class Subclass: BaseClass {

}

I started programming in Objective-C around 2010, and the relevant rules of the designated initialiser pattern are burned into my brain:

  • Rule 1 — The subclasses’s designated initialisers must each call through to one of the designated initialisers of the superclass using super.
  • Rule 2 — Override all the superclass’s designated initialisers and call through to one of the subclasses’s designated initialisers using self.

If you’ve used Swift but not Objective-C, you’re probably familiar with the first rule, but possibly not the second. We’ll understand why shortly.

To read more, see Apple’s documentation on Class Inheritance and Initialization(opens in a new tab) in The Swift Programming Language and Object Initialization(opens in a new tab) in Adopting Modern Objective-C.

An example… leading to the problem with the designated initialiser oattern

Let’s say we’re a fictional customer of PSPDFKit building a social networking app for sharing PDFs. We need to subclass the Document class from PSPDFKit to add a property, numberOfLikes. The relevant part of the interface of Document is:

class Document {

init(dataProviders: [DataProviding], loadCheckpointIfAvailable: Bool)

}

We implement rule 1 by defining the designated initialiser for our subclass, SocialDocument:

class SocialDocument: Document {

let numberOfLikes: Int

init(dataProviders: [DataProviding], numberOfLikes: Int) {

self.numberOfLikes = numberOfLikes

super.init(dataProviders: dataProviders, loadCheckpointIfAvailable: false)

}

}

Rule 2 is tricky though; we need to know the number of likes. Perhaps we could override the superclass’s designated initialisers and just set the number of likes to zero:

class SocialDocument: Document {

override convenience init(dataProviders: [DataProviding], loadCheckpointIfAvailable: Bool) {

self.init(dataProviders: dataProviders, numberOfLikes: 0)

}

}

However, let’s say we really need that number of likes; we can’t simply assume zero. We could treat this as a programmer error and abort the process. A practical alternative interpretation of the second rule is:

  • Alternative rule 2 — Override all the superclass’s designated initialisers and cleanly terminate the process with a clear error message.

Our implementation would then look like this:

class SocialDocument: Document {

override convenience init(dataProviders: [DataProviding], loadCheckpointIfAvailable: Bool) {

fatalError("Calling initializer 'init(dataProviders:loadCheckpointIfAvailable:)' is not allowed for SocialDocument.")

}

}

Of course, in Swift, a better solution is to not override this initialiser and then the compiler won’t let us call it. The second rule isn’t needed in Swift.

However, the compiler can only enforce this in Swift. If your class might be accessed from Objective-C, the superclasses’s designated initialiser might be called. We’ll cover why later on. Behind the scenes, the Swift compiler will synthesise an override of the superclass’s designated initialisers to cleanly crash, as shown above, except the error message used is Use of unimplemented initializer 'init(dataProviders:loadCheckpointIfAvailable:)'.

As a side note, in our Objective-C code in PSPDFKit, we follow a pattern similar to Swift when we want to change the designated initialisers in a subclass:

  • In our interface, mark the superclass’s designated initialisers as unavailable. This means the compiler would catch most cases of trying to call the wrong initialiser. However unlike in Swift, it won’t catch all cases.
  • In our implementation, override the superclass’s designated initialisers and cleanly terminate the process.

Don’t worry; we’re not dinosaurs writing these overrides for every class: We use C preprocessor macros!

NSCopying

NSCopying(opens in a new tab), from Apple’s Foundation framework, is a “protocol that objects adopt to provide functional copies of themselves”. The copyright at the top of the header file where this is protocol is declared, NSObject.h, starts in 1994.

The documentation for copyWithZone:(opens in a new tab) says “the exact nature of the copy is determined by the class”. I interpret this to mean it’s up to us whether or not to return a subclass.

Swift wisely avoids supporting copying instances of classes in its standard library. Instead, Swift structs are value types, so they’re implicitly copyable, and they don’t have any subclassing concerns.

NSCopying the unsafe way

Let’s recap the situation:

  • The Document class in our framework has the designated initialiser init(dataProviders:loadCheckpointIfAvailable:).
  • The customer creates SocialDocument as a subclass of Document with the designated initialiser init(dataProviders:numberOfLikes:).

Document conforms to the NSCopying protocol. Internally, PSPDFKit creates copies of document objects in some cases.

We can reproduce the crash the customer saw like this:

let originalDocument = SocialDocument(dataProviders: [fileDataProvider], numberOfLikes: 5)

let copiedDocument = originalDocument.copy() // 💥

On the call to copy(), the process crashes with:

Fatal error: Use of unimplemented initializer ‘init(dataProviders:loadCheckpointIfAvailable:)’

This takes us back to the discussion of “alternative rule 2” above. This crash happens because, behind the scenes, the Swift compiler synthesises overrides of a superclass’s designated initialisers. These overridden initialisers crash to prevent objects from being incorrectly initialised from Objective-C.

PSPDFKit’s implementation of NSCopying on Document is in Objective-C. For all other code in this post, it doesn’t matter much whether Swift or Objective-C is used, but this is the crucial part that only shows the problem in Objective-C. PSPDFKit’s old implementation was like this:

- (id)copyWithZone:(nullable NSZone *)zone {

let copiedDocument = [[self.class alloc] initWithDataProviders:self.dataProviders loadCheckpointIfAvailable:NO];

// Apply some internal state to the new object.

return copiedDocument;

}

(We deliberately don’t copy the value of loadCheckpointIfAvailable, but this detail isn’t relevant.)

From a quick look on Stack Overflow, it seems [self.class alloc] is often a recommended way to create a copy in Objective-C. However, the problem is that the use of self.class dynamically looks up the subclass SocialDocument, but the code here in our framework has no idea that SocialDocument has changed the initialisation requirements.

Initialising a subclass like this would work fine if the designated initialiser pattern was followed to the letter (using “rule 2” discussed above), but this isn’t compatible with Swift’s safer approach to programming.

NSCopying in Swift

Let’s try to implement NSCopying for Document the same way as above, but using Swift. We use Self (with a capital S) as the Swift way to dynamically look up the class:

extension Document: NSCopying {

func copy(with zone: NSZone? = nil) -> Any {

let copiedDocument = Self(dataProviders: self.dataProviders, loadCheckpointIfAvailable: false) // ❌

// Apply some internal state to the new object.

// [...]

return copiedDocument

}

}

The compiler shows this error:

❌ Constructing an object of class type ‘Self’ with a metatype value must use a ‘required’ initializer

This is scary compiler engineer jargon, but what it basically says is that because the exact subclass will only be known when running the code (not when compiling), we can only call an initialiser marked as required. When an initialiser is required, the compiler enforces that the initialiser is available on all subclasses. In other words, if a class changes the initialisers, that subclass must also override the required initialiser.

Solution 1: Required initialiser

We can do what the compiler error says. We change the interface of Document so its designated initialiser is marked as required:

class Document {

required init(dataProviders: [DataProviding], loadCheckpointIfAvailable: Bool)

}

Document’s copy(with:) implementation shown above now compiles, but this means that subclasses of Document must override this initialiser, so SocialDocument will have this compiler error:

❌ ‘required’ initializer ‘init(dataProviders:loadCheckpointIfAvailable:)’ must be provided by subclass of ‘Document’

Since numberOfLikes is an essential state, the best compromise is to change that property from let to var, and then to override copyWith(:) to copy this value:

class SocialDocument: Document {

// We want this to be `let`, but it must be `var` because

// of the required initialiser from the superclass.

var numberOfLikes: Int

// The designated initialiser (no change here).

init(dataProviders: [DataProviding], numberOfLikes: Int) {

self.numberOfLikes = numberOfLikes

super.init(dataProviders: dataProviders, loadCheckpointIfAvailable: false)

}

// Override the required initialiser. Use a temporary

// wrong value of zero for the number of likes.

required convenience init(dataProviders: [DataProviding], loadCheckpointIfAvailable: Bool) {

self.init(dataProviders: dataProviders, numberOfLikes: 0)

}

override func copy(with zone: NSZone? = nil) -> Any {

let copiedDocument = super.copy(with: zone) as! SocialDocument

copiedDocument.numberOfLikes = self.numberOfLikes

return copiedDocument

}

}

(Note that the compiler enforces that the override of the required initialiser is also marked required, since this requirement would cascade if there was a subclass of SocialDocument.)

This solution isn’t great: Subclasses can’t add any read-only (let) properties. The subclasses need to override an initialiser using a placeholder value and then override copy(with:) to replace this temporary value with the real value.

As an aside, our Document class is internally implemented as an Objective-C class called PSPDFDocument. To implement a class in Objective-C with an initialiser that maps to a required initialiser in Swift, we’d have to (perhaps non-obviously) declare the initialiser in a @protocol instead of in the class @interface, and then make the class conform to that protocol. Thanks to Kostiantyn Herasimov for pointing this out(opens in a new tab). This setup wouldn’t be ideal, because the extra level of abstraction would show up in our public API even though it’s not something our customers would need to interact with.

Solution 2: Copying creates an instance of the base class

An alternative approach is for Document to implement copy(with:) without attempting to create an instance of a subclass:

extension Document: NSCopying {

func copy(with zone: NSZone? = nil) -> Any {

let copiedDocument = Document(dataProviders: self.dataProviders, loadCheckpointIfAvailable: false)

// Apply some internal state to the new object.

// [...]

return copiedDocument

}

}

With this, calling copy() on an instance of SocialDocument will return an instance of Document rather than SocialDocument. This is a limitation, but overall, I think it’s easier to understand and is preferable to all the requirements of solution 1.

If Document were a simpler type where all state that should be copied was public, then subclasses that required copying to create instances of the subclass could override copy(with:) without calling super, like this:

class SocialDocument: Document {

override func copy(with zone: NSZone? = nil) -> Any {

let copiedDocument = SocialDocument(dataProviders: self.dataProviders, numberOfLikes: self.numberOfLikes)

// Apply mutable state from the superclass.

// [...]

return copiedDocument

}

}

In reality, our Document class has some internal state that needs copying, and we didn’t pursue this further. If our customers let us know that having NSCopying create instances of subclasses is important for them, we’ll come back to this.

Conclusions

The Objective-C designated initialiser pattern has the limitation that subclasses can’t add additional initialisation requirements, because the designated initialisers of the superclass still need to be functional.

There isn’t a nice way to make copying subclasses work while still adhering to Swift’s principle of reducing the amount of mutable state by using let to create read-only properties. The nicest way is for an implementation of NSCopying to not attempt to create instances of a subclass.

NSCopying and Swift come from different eras and don’t like each other, so we should avoid using them together if possible. Swift avoids most of the need for copying instances of classes by making structs first-class value types.