DEV Community

Swift Classes β€” Why Copying a Class Isn't What You Think πŸ“‹

Gamya on July 21, 2026

Okay. This one is going to feel weird at first. I'm going to warn you in advance so you don't think something is broken when you try it. Here's wh...
Collapse
 
vinimabreu profile image
Vinicius Pereira

Good analogy. The tool that makes it concrete is the identity operator ===: naruto === naruto2 is true because they point at the same signpost, and after a real copy() it flips to false (== compares values, === compares identity). In practice the bug is rarely that obvious line, it is a shared instance passed into a function or held in an array that something mutates far from where you assigned it.

Collapse
 
gamya_m profile image
Gamya

The === identity operator is such a clean concrete tool for exactly this β€” because == telling you two things are equal still doesn't tell you whether they're the same object or two objects that happen to have the same values, and that distinction is exactly what matters when you're debugging shared mutation. The "far from where you assigned it" point is where it gets genuinely painful in practice too β€” the mutation that breaks your view happens three function calls away from the assignment, nothing looks wrong at either end, and the signpost is the invisible thread connecting them.

Collapse
 
vinimabreu profile image
Vinicius Pereira

"Nothing looks wrong at either end" is exactly why these are so slow to find: both ends pass code review in isolation. The trick I lean on is to stop trusting your eyes and make identity observable: print or log the object identifier at the assignment and at the mutation, and if the two match, you have found your invisible thread in one run instead of an afternoon of staring. Turning "same object?" from a guess into a printed fact is usually the whole fix.

Thread Thread
 
gamya_m profile image
Gamya

"Turning same object? from a guess into a printed fact" is the right move and I think the reason people don't reach for it faster is that the bug doesn't look like an identity problem from either end, so it doesn't occur to you to check identity. You're staring at a mutation that shouldn't have happened and looking for where the bad code is, not looking for whether two variables are accidentally the same variable. Printing the object identifier shifts the question from "what is wrong with this code" to "are these the same thing", which is a much faster path to the answer once you know to ask it.

Thread Thread
 
vinimabreu profile image
Vinicius Pereira

That is the sharper framing, and it explains why the fix is cheap but the discovery is expensive: you are debugging the mutation, so you search where the write happens, and the write is innocent. The guilty line is an assignment that ran ten minutes earlier and looked like housekeeping.

The habit I built from getting burned by it: whenever a value changes and no local code changed it, ask "same thing or different thing?" before asking "what is wrong here?" Reference types, mutable defaults in Python, shared dict passed into two objects, same shape every time. Cheap question, and it either eliminates a whole class of suspects or hands you the answer.

Thread Thread
 
gamya_m profile image
Gamya

"Same thing or different thing?" before "what is wrong here?" is such a good first triage question, because it collapses the search space immediately. If the answer is "same thing", you're looking at a shared reference problem and you stop hunting for a buggy write. If it's "different thing", the identity question is closed and you can focus on the logic. Either way you're not spending an hour in the wrong half of the problem. The fact that it covers reference types, mutable defaults, shared dicts, all the same shape across languages makes it genuinely worth building into muscle memory rather than reaching for it only when you're already stuck.

Collapse
 
merbayerp profile image
Mustafa ERBAY

Great explanation. The signpost analogy makes reference semantics much easier to visualize.

One thing I’d add is that implementing copy() becomes much more complex once a class contains nested reference types. A shallow copy may still share internal objects, while a deep copy creates independent copies of the entire object graph. That’s where copying semantics become an architectural decision rather than just a convenience method. Nice article!

Collapse
 
gamya_m profile image
Gamya

Really good addition β€” and you're right that the copy() method I showed is a shallow copy, which works cleanly for the simple case but quietly breaks down the moment the class holds references to other objects. A deep copy requiring you to recursively copy the entire object graph is a genuinely different problem, and one where the decision about how deep to go becomes architectural rather than mechanical. Worth a follow-up once the series gets to more complex data models β€” glad you flagged it! 🌸

Collapse
 
merbayerp profile image
Mustafa ERBAY

Exactly. Once object graphs become more complex, β€œcopy” stops being a language feature and becomes part of the domain model. Different applications can legitimately require different copying semantics, so there rarely is a single correct implementation.

Thread Thread
 
gamya_m profile image
Gamya

"Part of the domain model" is the right framing β€” because once you're making decisions about how deep to copy, which references to share and which to clone, you're not just implementing a Swift pattern, you're encoding assumptions about your application's data ownership model. Two different apps with the same class structure could legitimately need completely different copy semantics depending on what "a copy" means in their specific context. That's why there's no universal Copyable that Swift can just provide β€” the language can give you the tools, but the semantics have to come from you.

Thread Thread
 
merbayerp profile image
Mustafa ERBAY

Glad we’re on the same page. I think that’s the key takeaway: the language can define the mechanics, but the domain defines the meaning.

Thanks for the thoughtful discussion. I really enjoyed seeing the conversation evolve beyond Swift syntax into API and architectural design. Looking forward to the rest of the series!

Thread Thread
 
gamya_m profile image
Gamya

"The language defines the mechanics, but the domain defines the meaning" is a great line to close on, and honestly one of the best takeaways from this whole thread. Really enjoyed the conversation too, it pushed the ideas a lot further than the article itself did. Looking forward to more of these as the series continues!