<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	xmlns:media="http://search.yahoo.com/mrss/"
>

<channel>
	<title>NSCopying &#8211; Wade Tregaskis</title>
	<atom:link href="https://wadetregaskis.com/tags/nscopying/feed/" rel="self" type="application/rss+xml" />
	<link>https://wadetregaskis.com</link>
	<description></description>
	<lastBuildDate>Tue, 16 Jul 2024 00:43:12 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	

<image>
	<url>https://wadetregaskis.com/wp-content/uploads/2016/03/Stitch-512x512-1-256x256.png</url>
	<title>NSCopying &#8211; Wade Tregaskis</title>
	<link>https://wadetregaskis.com</link>
	<width>32</width>
	<height>32</height>
</image> 
<site xmlns="com-wordpress:feed-additions:1">226351702</site>	<item>
		<title>NSCopyObject, the griefer that keeps on griefing</title>
		<link>https://wadetregaskis.com/nscopyobject-the-griefer-that-keeps-on-griefing/</link>
					<comments>https://wadetregaskis.com/nscopyobject-the-griefer-that-keeps-on-griefing/#respond</comments>
		
		<dc:creator><![CDATA[]]></dc:creator>
		<pubDate>Tue, 16 Jul 2024 00:42:07 +0000</pubDate>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[Apple]]></category>
		<category><![CDATA[ARC]]></category>
		<category><![CDATA[Broken by design]]></category>
		<category><![CDATA[copy(with:)]]></category>
		<category><![CDATA[fixupCopiedIvars]]></category>
		<category><![CDATA[NeXT]]></category>
		<category><![CDATA[NSAnimation]]></category>
		<category><![CDATA[NSCell]]></category>
		<category><![CDATA[NSCopying]]></category>
		<category><![CDATA[NSImageRep]]></category>
		<category><![CDATA[Objective-C]]></category>
		<category><![CDATA[Sad]]></category>
		<category><![CDATA[Swift]]></category>
		<guid isPermaLink="false">https://wadetregaskis.com/?p=8281</guid>

					<description><![CDATA[NSCopyObject is a very old Foundation function - pre-dating Mac OS X entirely; from the NeXT era - that was originally basically just memcpy, but now it's complicated. A lot more complicated… <a class="read-more-link" href="https://wadetregaskis.com/nscopyobject-the-griefer-that-keeps-on-griefing/" data-wpel-link="internal">Read more</a>]]></description>
										<content:encoded><![CDATA[
<p><code><a href="https://developer.apple.com/documentation/foundation/1587928-nscopyobject" data-wpel-link="external" target="_blank" rel="external noopener">NSCopyObject</a></code> is a very old Foundation function &#8211; pre-dating Mac OS X entirely; from the NeXT era &#8211; that was <em>originally</em> basically just <code><a href="https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/memcpy.3.html" data-wpel-link="external" target="_blank" rel="external noopener">memcpy</a></code>, but now it&#8217;s complicated.  A lot more complicated.</p>



<h2 class="wp-block-heading">What <code>NSCopyObject</code> does</h2>



<p>Its implementation <em>currently</em> starts with essentially:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" data-code="id NSCopyObject(id object, NSUInteger extraBytes, NSZone *zone) {
    if (nil == object) {
        return nil;
    }
    
    id copy = object_copy(object, extraBytes);
    object_setClass(copy, objc_opt_class(object));
    return copy;
}" style="color:#000000;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">id</span><span style="color: #000000"> </span><span style="color: #795E26">NSCopyObject</span><span style="color: #000000">(</span><span style="color: #0000FF">id</span><span style="color: #000000"> object, </span><span style="color: #267F99">NSUInteger</span><span style="color: #000000"> extraBytes, </span><span style="color: #267F99">NSZone</span><span style="color: #000000"> *zone) {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #AF00DB">if</span><span style="color: #000000"> (</span><span style="color: #0000FF">nil</span><span style="color: #000000"> == object) {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #AF00DB">return</span><span style="color: #000000"> </span><span style="color: #0000FF">nil</span><span style="color: #000000">;</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"><span style="color: #000000">    </span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">id</span><span style="color: #000000"> copy = </span><span style="color: #795E26">object_copy</span><span style="color: #000000">(object, extraBytes);</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #795E26">object_setClass</span><span style="color: #000000">(copy, </span><span style="color: #795E26">objc_opt_class</span><span style="color: #000000">(object));</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #AF00DB">return</span><span style="color: #000000"> copy;</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p>…where <a href="https://github.com/apple-oss-distributions/objc4/blob/01edf1705fbc3ff78a423cd21e03dfc21eb4d780/runtime/objc-runtime-new.mm#L9057" data-wpel-link="external" target="_blank" rel="external noopener">object_copy</a> et al are part of <a href="https://github.com/apple-oss-distributions/objc4" data-wpel-link="external" target="_blank" rel="external noopener">the Objective-C runtime</a>.  <code>object_copy</code> and its callees are not trivial, so I won&#8217;t repeat them here.  The key parts are:</p>



<ol class="wp-block-list">
<li>The malloc in <code><a href="https://github.com/apple-oss-distributions/objc4/blob/01edf1705fbc3ff78a423cd21e03dfc21eb4d780/runtime/objc-runtime-new.mm#L8981" data-wpel-link="external" target="_blank" rel="external noopener">_class_createInstance</a></code> (allocate space for the copy).</li>



<li>The <code>memmove</code> in <code>object_copy</code> (naively copy the raw bytes over).</li>



<li>The call from <code>object_copy</code> to <code><a href="https://github.com/apple-oss-distributions/objc4/blob/01edf1705fbc3ff78a423cd21e03dfc21eb4d780/runtime/objc-class.mm#L535" data-wpel-link="external" target="_blank" rel="external noopener">fixupCopiedIvars</a></code> (half-heartedly attempt to fix the damage).</li>
</ol>



<p><code>fixupCopiedIvars</code> is notable.  It was added by necessity when <a href="https://en.wikipedia.org/wiki/Automatic_Reference_Counting" data-wpel-link="external" target="_blank" rel="external noopener">ARC</a> was introduced to the Objective-C runtime, in Mac OS X 10.6 (Snow Leopard) in 2006.  ARC added metadata to Objective-C classes to convey which instance variables were retain-counted object references, so that it could manage them automagically at runtime (not just for copying objects, but more importantly for deallocating them).  <code>fixupCopiedIvars</code> uses that metadata to identify things it has to retain (strongly or weakly) in the new copy.</p>



<p>So that should work great, right?  The copy operation increments the retain count of all shared objects the new copy references, like you&#8217;d expect?</p>


<div class="wp-block-image">
<figure class="aligncenter size-full"><img fetchpriority="high" decoding="async" width="340" height="266" src="https://wadetregaskis.com/wp-content/uploads/2024/07/grumpy-cat-no.avif" alt="Grumpy Cat frowning, with the caption &quot;NO&quot;." class="wp-image-8282" srcset="https://wadetregaskis.com/wp-content/uploads/2024/07/grumpy-cat-no.avif 340w, https://wadetregaskis.com/wp-content/uploads/2024/07/grumpy-cat-no-256x200.avif 256w, https://wadetregaskis.com/wp-content/uploads/2024/07/grumpy-cat-no@2x.avif 680w" sizes="(max-width: 340px) 100vw, 340px" /></figure>
</div>


<p>That metadata is incomplete.  It only works for Objective-C ivars managed by ARC.  i.e. <em>not</em> C++ ivars or Swift stored properties, nor even Objective-C ivars that aren&#8217;t using ARC<sup data-fn="cf3bfb82-cbf6-40c4-817f-3092a63f4021" class="fn"><a href="#cf3bfb82-cbf6-40c4-817f-3092a63f4021" id="cf3bfb82-cbf6-40c4-817f-3092a63f4021-link">1</a></sup>.</p>



<h2 class="wp-block-heading">But I don&#8217;t use <code>NSCopyObject</code>…?</h2>



<p>Almost nobody <em>intentionally</em> uses <code>NSCopyObject</code>, but your superclass might, and therefore you might.  Ever subclassed <code><a href="https://developer.apple.com/documentation/appkit/nscell" data-wpel-link="external" target="_blank" rel="external noopener">NSCell</a></code> or <code><a href="https://developer.apple.com/documentation/appkit/nsanimation" data-wpel-link="external" target="_blank" rel="external noopener">NSAnimation</a></code>, for example?</p>



<p><a href="https://forums.swift.org/t/why-would-deinit-be-called-when-retain-count-is-non-zero/72924" data-wpel-link="external" target="_blank" rel="external noopener">I happened to hit this</a> when subclassing <code><a href="https://developer.apple.com/documentation/appkit/nsbitmapimagerep" data-wpel-link="external" target="_blank" rel="external noopener">NSBitmapImageRep</a></code> (and I&#8217;m very grateful to <a href="https://forums.swift.org/u/ksluder" data-wpel-link="external" target="_blank" rel="external noopener">Kyle Sluder</a> for so quickly identifying the problem &#8211; it could have taken me forever to figure it out, otherwise).</p>



<p>If your superclass uses <code>NSCopyObject</code>, it&#8217;s now your problem just as much as if you&#8217;d used <code>NSCopyObject</code> directly, whether you like it or not.</p>



<p>And even more problematically, whether you <em>know</em> it or not.  If your superclass is defined by a 3rd party framework / library, or anything that&#8217;s closed source, you might have no idea whether it uses <code>NSCopyObject</code> currently.  Worse, you have no control over whether it will or will not use it in future (though anyone that <em>adds</em> a use of <code>NSCopyObject</code> at this point had better hope the atheists are right).</p>



<h2 class="wp-block-heading">So how do I defend against <code>NSCopyObject</code>?</h2>



<h3 class="wp-block-heading">Objective-C</h3>



<p>Pre-ARC it used to be <em>relatively</em> easy to work around this, in Objective-C.  You &#8220;just&#8221; had to manually <code><a href="https://developer.apple.com/documentation/objectivec/1418956-nsobject/1571946-retain?language=objc" data-wpel-link="external" target="_blank" rel="external noopener">retain</a></code> all your subclasses&#8217; reference ivars &#8211; and manually copy some others, like non-ref-counted mutable or mortal buffers, etc.</p>



<p>But that generally isn&#8217;t possible with ARC &#8211; under which you cannot explicitly call <code>retain</code>.  Worse:</p>



<ul class="wp-block-list">
<li>There&#8217;s still <a href="https://www.mikeash.com/pyblog/friday-qa-2010-08-27-defensive-programming-in-cocoa.html" data-wpel-link="external" target="_blank" rel="external noopener">prominent</a> guides scattered about the web that push you unequivocally to use <code>retain</code>, which is not just impossible to do directly under ARC, but flat-out <em>wrong</em> even if you do figure out one of the &#8220;clever&#8221; ways to do it (you&#8217;ll end up <em>over</em>-retaining your ARC-managed references, causing memory leaks).</li>



<li>There&#8217;s also <a href="https://dohle.wordpress.com/2012/05/21/hello-world/" data-wpel-link="external" target="_blank" rel="external noopener">pages</a> lingering on the web that claim that merely turning on ARC will magically solve the problem (it <em>might</em>, but it&#8217;s not a panacea).</li>
</ul>



<p><a href="https://wiki.herzbube.ch/index.php/LearningObjectiveC#Object_copy" data-wpel-link="external" target="_blank" rel="external noopener">Some</a> <a href="https://robnapier.net/implementing-nscopying" data-wpel-link="external" target="_blank" rel="external noopener">guides</a> specify a better method, which is to manually zero out the copied object&#8217;s ivars and then repopulate them via formal property setters.  That actually works with or without ARC, although it may break &#8211; causing memory leaks &#8211; if the superclass ever stops using <code>NSCopyObject</code> (or if <code>NSCopyObject</code> ever gets upgraded to understand reference-counted ivars that it currently does not).  It&#8217;s also only possible in Objective-C because Swift doesn&#8217;t provide direct access to instance variables.</p>



<p>Keep in mind that any reference-typed ivars which are not strong or weak Objective-C objects managed by ARC will still, always need to be handled manually.  e.g. pointers to manually-managed memory buffers.</p>



<h3 class="wp-block-heading">Swift</h3>



<p>Ironically, Swift&#8217;s attempts to prevent incorrect code actually make it harder to write correct code in this case.  What you <em>want</em> to do is &#8211; like the Objective-C implementation &#8211; to just zero out the references and re-assign them like normal properties.  Zeroing them out <em>without triggering a release</em> basically undoes the mistaken <code>memcpy</code> that <code>NSCopyObject</code> did.  But Swift won&#8217;t let you.</p>



<p>Worse, <a href="https://github.com/swiftlang/swift/issues/47333" data-wpel-link="external" target="_blank" rel="external noopener">this has been known</a> for most of Swift&#8217;s existence and nothing has been done about it.</p>



<p>Simply setting the property to <code>nil</code> will cause it to be erroneously released, which may immediately deallocate the object and ultimately cause a crash or memory corruption.  Even if it doesn&#8217;t happen to deallocate the object, it&#8217;ll negate the retain you do during the assignment, making all your effort moot.</p>



<figure class="wp-block-pullquote"><blockquote><p>Strictly-speaking, the only safe thing to do is override <code><a href="https://developer.apple.com/documentation/foundation/nscopying/1410311-copy" data-wpel-link="external" target="_blank" rel="external noopener">copy(with:)</a></code> and not call super, but rather create a new instance from scratch.</p></blockquote></figure>



<p>That&#8217;s pretty heavy-handed, though, and not always possible (e.g. <code>NSImageRep</code>, as used by e.g. <code>NSBitmapImageRep</code>, does some special magic in its copy implementation which you cannot practically replicate).</p>



<p>It appears that the best you can do is <em>assume</em> the superclass will always use <code>NSCopyObject</code>, if it does currently, and just manually increment the retain count.  Like Objective-C with ARC, the language &amp; standard library really don&#8217;t want you to actually do this, but at least in Swift it&#8217;s relatively straightforward:</p>



<div class="wp-block-kevinbatdorf-code-block-pro padding-disabled" data-code-block-pro-font-family="" style="font-size:.875rem;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span role="button" tabindex="0" data-code="override func copy(with zone: NSZone? = nil) -&gt; Any {
    let result = super.copy(with: zone)
    
    if result.myProperty === self.myProperty {
        _ = Unmanaged.passRetained(myProperty)
    } else {
        result.myProperty = self.myProperty
    }
}" style="color:#000000;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki light-plus" style="background-color: #FFFFFF" tabindex="0"><code><span class="line"><span style="color: #0000FF">override</span><span style="color: #000000"> </span><span style="color: #0000FF">func</span><span style="color: #000000"> </span><span style="color: #795E26">copy</span><span style="color: #000000">(</span><span style="color: #795E26">with</span><span style="color: #000000"> </span><span style="color: #001080">zone</span><span style="color: #000000">: NSZone? = </span><span style="color: #0000FF">nil</span><span style="color: #000000">) -&gt; </span><span style="color: #267F99">Any</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #0000FF">let</span><span style="color: #000000"> result = </span><span style="color: #0000FF">super</span><span style="color: #000000">.</span><span style="color: #795E26">copy</span><span style="color: #000000">(</span><span style="color: #795E26">with</span><span style="color: #000000">: zone)</span></span>
<span class="line"><span style="color: #000000">    </span></span>
<span class="line"><span style="color: #000000">    </span><span style="color: #AF00DB">if</span><span style="color: #000000"> result.</span><span style="color: #001080">myProperty</span><span style="color: #000000"> === </span><span style="color: #0000FF">self</span><span style="color: #000000">.</span><span style="color: #001080">myProperty</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">        </span><span style="color: #001080">_</span><span style="color: #000000"> = </span><span style="color: #267F99">Unmanaged</span><span style="color: #000000">.</span><span style="color: #795E26">passRetained</span><span style="color: #000000">(myProperty)</span></span>
<span class="line"><span style="color: #000000">    } </span><span style="color: #AF00DB">else</span><span style="color: #000000"> {</span></span>
<span class="line"><span style="color: #000000">        result.</span><span style="color: #001080">myProperty</span><span style="color: #000000"> = </span><span style="color: #0000FF">self</span><span style="color: #000000">.</span><span style="color: #001080">myProperty</span></span>
<span class="line"><span style="color: #000000">    }</span></span>
<span class="line"><span style="color: #000000">}</span></span></code></pre></div>



<p>The conditional <em>might</em> help protect you if the superclass stops using <code>NSCopyObject</code> in future &#8211; in that case, it&#8217;ll <em>probably</em> cause <code>myProperty</code> to default to nil (or to be assigned to some other instance, which you can discard), in which case you just want to assign to it normally.</p>



<p>In the interim &#8211; while <code>NSCopyObject</code> is in use, at least &#8211; the <code>myProperty</code> pointer will be copied verbatim and you have to assume it requires the extra, manual retain.  It&#8217;s <em>not</em> future-proof &#8211; it&#8217;s possible for the superclass to copy the pointer verbatim <em>and</em> increment the retain count for you &#8211; but at least in that case you &#8220;merely&#8221; get a memory leak, rather than a crash or memory corruption.</p>



<h2 class="wp-block-heading">Do as Apple says, not as Apple does</h2>



<p>The most frustrating part of all of this is that this is entirely Apple&#8217;s fault.  Sure, you can argue it&#8217;s not their fault that NeXT added this vile function to Foundation; that Apple &#8220;merely&#8221; inherited it and were &#8220;forced&#8221; to keep for backwards compatibility.  But it&#8217;s <em>entirely</em> Apple&#8217;s choice to have kept using it all this time, in their core frameworks, even while they&#8217;ve been telling everyone else to never use it.</p>



<p><code>NSCopyObject</code> has been a known problem-maker pretty much forever &#8211; it was a terrible idea right from the outset.  Blindly copying the bytes of an object instance, and just hoping that somehow that works correctly &#8211; in an <em>object-oriented</em> language derived from Smalltalk where <a href="https://developer.apple.com/documentation/foundation/nsnumber" data-wpel-link="external" target="_blank" rel="external noopener">even numbers are often reference types</a> &#8211; is farcical.</p>



<p>The introduction of ARC (in 2008) didn&#8217;t really help anything, as although it changed <code>NSCopyObject</code> to properly retain <em>ARC</em>-managed ivars, it did nothing for non-ARC-managed ivars (remember that ARC can be enabled in one library but not in another, and libraries can subclass each others&#8217; classes).</p>



<p><code>NSCopyObject</code> has been officially deprecated since 2012:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>The NSCopyObject() function has been deprecated. It has always been a dangerous function to use, except in the implementation of copy methods<sup data-fn="19a637fd-3fbb-43d3-a9c0-29896c849e94" class="fn"><a href="#19a637fd-3fbb-43d3-a9c0-29896c849e94" id="19a637fd-3fbb-43d3-a9c0-29896c849e94-link">2</a></sup>, and only then with care.</p>
<cite><a href="https://developer.apple.com/library/archive/releasenotes/Foundation/RN-FoundationOlderNotes/index.html#X10_8Notes" data-wpel-link="external" target="_blank" rel="external noopener">Foundation Release Notes for OS X 10.8 Mountain Lion and iOS 6</a></cite></blockquote>



<p>…though Apple officially told everyone not to use it in 2008:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>This function is dangerous and very difficult to use correctly. It&#8217;s [sic] use as part of -copyWithZone: by any class that can be subclassed, is highly error prone. This function is known to fail for objects with embedded retain count ivars, singletons, and C++ ivars, and other circumstances.</p>
<cite><a href="https://developer.apple.com/library/archive/releasenotes/Foundation/RN-FoundationOlderNotes/index.html#X10_6Notes" data-wpel-link="external" target="_blank" rel="external noopener">Foundation Release Notes for Mac OS X 10.6 Snow Leopard</a></cite></blockquote>



<p>And this was all still a decade or more after it was known that <code>NSCopyObject</code> was fundamentally evil, e.g. <a href="https://www.mulle-kybernetik.com/weblog/2004/argh_wasted_two_hours_on_stupi.html" data-wpel-link="external" target="_blank" rel="external noopener">NSCell</a>, and <a href="https://mail.gnu.org/archive/html/discuss-gnustep/2000-09/msg00097.html" data-wpel-link="external" target="_blank" rel="external noopener">GnuStep&#8217;s broken NSControl</a>.</p>



<p>And yet, Apple <em>still</em> use <code>NSCopyObject</code> themselves <em>to this very day</em>, in their own applications and frameworks &#8211; including major frameworks like AppKit that almost all 3rd party developers rely on.  <code>NSCell</code> is <em>still</em> broken, three decades later, as is <code>NSImage</code> &amp; <code>NSImageRep</code>, and <code>NSAnimation</code>.  Most of those are <em>explicitly designed to be subclassed</em>, despite Apple&#8217;s own very clear instructions to never mix subclassing with <code>NSCopyObject</code>.</p>



<p>Admittedly it&#8217;s not trivial for Apple to remove the <code>NSCopyObject</code> use &#8211; alas, <em>because</em> people have had to code myriad hacky workarounds to it, Apple now has to be careful not to break those workarounds.  That might even preclude fixing the existing code paths; it might require a <em>replacement</em> copy mechanism.  Which leads to…</p>



<h2 class="wp-block-heading">Tangent: NSCopying considered harmful</h2>



<p>The big driver of <code>NSCopyObject</code> use has long been <code><a href="https://developer.apple.com/documentation/foundation/nscopying" data-wpel-link="external" target="_blank" rel="external noopener">NSCopying</a></code>.  Classes that intend to be subclassed &#8211; but also semantically should support copying i.e. <code>NSCopying</code> &#8211; have long been making the mistake of thinking that means using <code>NSCopyObject</code>.  One need only read the NSCopying documentation, even <a href="https://preterhuman.net/macstuff/techpubs/macosx/System/Library/Frameworks/Foundation.framework/Versions/C/Resources/English.lproj/Documentation/Reference/ObjC_classic/Protocols/NSCopying.html" data-wpel-link="external" target="_blank" rel="external noopener">from before Mac OS X was even publicly released</a>, to see how dangerously fragile and error-prone <code>NSCopying</code> has always been.</p>



<p>Compounding the problem is that <code>NSCopying</code> <em>doesn&#8217;t work, by default, on subclasses</em>.  You <em>have</em> to override <code>copy(with:)</code> in every subclass<sup data-fn="712b988a-5cac-484c-9eaf-fc22bc3afc25" class="fn"><a href="#712b988a-5cac-484c-9eaf-fc22bc3afc25" id="712b988a-5cac-484c-9eaf-fc22bc3afc25-link">3</a></sup>, but the compiler does not enforce this, because in Objective-C (and alas Swift) protocol conformance is <em>assumed</em> inherited even when it cannot correctly be without explicit, extra work by the subclass.</p>


<ol class="wp-block-footnotes"><li id="cf3bfb82-cbf6-40c4-817f-3092a63f4021">Yes, it&#8217;s still possible to this day to write Objective-C without using ARC &#8211; <code><a href="https://clang.llvm.org/docs/AutomaticReferenceCounting.html#id8" data-wpel-link="external" target="_blank" rel="external noopener">-fno-objc-arc</a></code> / <code><a href="https://developer.apple.com/documentation/xcode/build-settings-reference#Objective-C-Automatic-Reference-Counting" data-wpel-link="external" target="_blank" rel="external noopener">CLANG_ENABLE_OBJC_ARC</a></code>.  There might even be valid (albeit unfortunate) use-cases for having to do so, such as for performance.<br><br>And even with ARC, it&#8217;s of course possible to have pointers to things which aren&#8217;t <code>NSObject</code>s and therefore aren&#8217;t handled by ARC, such as raw malloc allocations. <a href="#cf3bfb82-cbf6-40c4-817f-3092a63f4021-link" aria-label="Jump to footnote reference 1">↩︎</a></li><li id="19a637fd-3fbb-43d3-a9c0-29896c849e94">This is false and always has been (that it was safe to use in copy methods).  Apple&#8217;s false statements in the deprecation notices may ironically have caused even <em>more</em> instances of people using <code>NSCopyObject</code>. <a href="#19a637fd-3fbb-43d3-a9c0-29896c849e94-link" aria-label="Jump to footnote reference 2">↩︎</a></li><li id="712b988a-5cac-484c-9eaf-fc22bc3afc25">Any and all that add retain-counted ivars, Swift stored properties, or ivars of C++ types that have destructors. <a href="#712b988a-5cac-484c-9eaf-fc22bc3afc25-link" aria-label="Jump to footnote reference 3">↩︎</a></li></ol>]]></content:encoded>
					
					<wfw:commentRss>https://wadetregaskis.com/nscopyobject-the-griefer-that-keeps-on-griefing/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			<media:content url="https://wadetregaskis.com/wp-content/uploads/2024/07/grumpy-cat-no.avif" medium="image" />
<post-id xmlns="com-wordpress:feed-additions:1">8281</post-id>	</item>
	</channel>
</rss>
