Class copy constructors in Swift 4 for defensive copying

Swift tutorials by iosbrain.com Notice that Swift almost seems to frown on making a copy of a reference type, i.e., a copy of an instance of a class, or, as some would rather put it, getting a copy of an object. I’m not talking about getting another reference to a class, I’m talking about getting an entire, separate copy of a class instance. This frowning on class copying is not an accident. Swift’s language architects want the syntax and semantics of the language to be crystal clear. They want developers to be confident that reference types and value types will both have 1) distinct and obvious meanings and that both types will 2) behave consistently. But still, why not be able to safely make a copy of a class instance? I’ll show you how in this tutorial by borrowing the copy constructor concept from C++. In Swift, we’d call this a “copy initializer.” (NOTE: Yeah, yeah, yeah, I know about NSCopying in Cocoa and Objective-C.)


DEFENSIVE COPYING IN CLASSES

Making a copy of a class instance can give you a form of immutability — a baseline to which you can compare future changes to that instance, and/or a way to restore the instance’s state if something goes wrong later.

Let’s get started by opening Xcode and creating a new playground based on the iOS Blank template.

Default and designated initializers

Let’s start off by defining a class that represents a line. Since I’m thinking forwards for reuse, I’m going to build simple, minimal classes that can be reused. I want to take advantage of OOP inheritance, so while this Line class can be used for many applications, I’m making its member properties for representing the beginning point and end point of each line of type CGPoint. In this way, each Line instance can be used to draw lines in an iOS app via UIKit.

Oops…

Since my two properties don’t have initial values and are non-nil, I need to add at least one initializer that assigns values to all properties when an instance of my Line class in created. I find default initializers to be a great convenience especially when developing new code. They allow me to create an instance quickly and change it later, without having to take the time to type in a big long argument list:

Now let’s add the designated initializer that allows developers to create instances of the Line class with meaningful values:

Unintended mutation

So we Swift programmers should all understand by now what are the “perils” of reference semantics and types. I use the assignment operator (=), get another reference to a class instance, change a value of a property on the new reference, and — bang — the original instance is “unintentionally mutated:”

Let’s compare line1 and line2 with “the triple-equals identical-to operator (===)”:

Ah, ha! Well, not so ah, ha, as we’ve just gotten the results we’d expect from reference semantics: line1 and line2 hold identical references to the same instance.

What you lose with value semantics

Granted: It may be harder for an unintended mutation to occur in a value type, but that means sacrificing almost everything OOP offers, like polymorphism, virtual methods, late binding, inheritance hierarchies…

In C++, I would override the assignment operator for my class to help prevent unwanted changes. I’d also create a copy constructor. I can even turn off assignment in C++ by making my assignment operator private. Swift isn’t C++, but does support one of the ideas I’ve floated, but only if you build a custom version.

Swifty doesn’t like class instance copying

Swift is downright difficult about allowing copying of class instances:

It is not possible to overload the default assignment operator (=).

This is most likely due to the fact that Swift’s authors are trying their best to make reference semantics crystal clear.

The Copyable protocol and copy initializers

So let’s define a class-only protocol called Copyable so we can require our classes to implement copy initializers (copy constructors):

Now let’s make my Line class conform to Copyable:

Swift immediately complains that we haven’t conformed to the protocol yet:

So let’s take the Swift compiler up on its offer to help. It asks us “Do you want to add protocol stubs?” We do, so press “Fix,” and create the “stub.” Here’s what we get:

What do you think we’re going to do in this method? Remember that this is a required initializer and we’ve got several already. What would you do to copy the contents of a Line instance in order to get a new instance which is has the same data as the current one, but is not identical by definition of the === operator (the identical-to operator”)? C’mon. This should be easy:

Here’s the whole new Line class conforming to the Copyable protocol:

Let’s take our previous Line class testing code and turn reference copying into defensive copying. Let’s use the new copy constructor:

Look, ma! No changes:

Now let’s compare line1 and line2 with “the triple-equals identical-to operator (===)”:

Why the copy initializer is marked required

Notice that the Swift compiler itself marked my copy initializer, init(copy: Line), as required. You may be wondering why, as this method is already a stipulation of the Copyable protocol. Without going into a long-winded explanation, let me point you to this link, this one, and quote the official documentation:

Write the required modifier before the definition of a class initializer to indicate that every subclass of the class must implement that initializer … You must also write the required modifier before every subclass implementation of a required initializer, to indicate that the initializer requirement applies to further subclasses in the chain. You do not write the override modifier when overriding a required designated initializer…


Here’s an example in which I started a subclass of Line:

I must implement my copy initializer in every subclass and most likely override/customize it to account for new properties that are introduced in each subsequent descendant.

An even better copy initializer

If you try subclassing Line with the Copyable protocol as it now stands, you may find that your code gets a bit messy. It can be done, but the code isn’t semantically clear by my standards.

A new Copyable protocol

Let me redefine the Copyable protocol like so:

The copy() method you just saw is technically not an initializer, but you’ll see that while it’s not called init, it does call init. It’s a method that makes a separate, independent copy of a class instance. It still doesn’t copy a reference, it copies the current contents of an instance’s properties and returns a fresh instance of classes that conform to Copyable.

Let me take a first stab at making Line adopt the Copyable protocol. Of course, Swift immediately flags the fact that I don’t have a copy() implementation, am thusly non-conformant with Copyable, and offers to add a stub for copy(). I accept the stub, implement its contents, but still have one issue. Here’s the code:

Marking the designated initializer required

The pending issue I mentioned is “Constructing an object of class type ‘Self’ with a metatype value must use a ‘required’ initializer.” Here’s an image:

I can fix this easily by prefixing my designated initializer with the required keyword. We already went over the reason for this in the preceding section above entitled “Why the copy initializer is marked required.” Here’s the new code:

Subclassing

Now I’ll make a subclass, DrawableLine, of Line:

Notice again, there’s a catch, but a catch we should’ve expected because the Line class’s init is marked as required: “‘required’ initializer ‘init(beginPoint:endPoint:)’ must be provided by subclass of ‘Line'”. Here’s an image of the error and suggestion to add a stub for the parent’s designated initializer:

I added the stub and encoded it, giving us this:

What about the subclass’s copy initializer?

I could just let DrawableLine inherit my “copy initializer,” copy(), from Line, but that would be silly. (I’m assuming that inheriting copy() satisfies Copyable.) What about the subclass’s new member properties, color and width? I need to override copy() for DrawableLine:

But now I get “Constructing an object of class type ‘Self’ with a metatype value must use a ‘required’ initializer” (see comment in code immediately above). Again, I can fix this easily by prefixing my designated initializer for DrawableLine with the required keyword (see line 45 below). We already went over the reason for this in the preceding section above entitled “Why the copy initializer is marked required.” Here’s all the new class code plus some test code:

Note that I was able to make a change to the drawableLine instance by changing drawableLine1, a reference to the same instance. But I made a copy, drawableLineCopy, of drawableLine. Because of my copy initializer, changing the copy had no effect on the original instance. Here’s a visual representation of my test code:

Finally, look closely at my use of the === operator:

CONCLUSION

Now you’ve got a simple tool to use in your programming to help you make defensive copies of your class instances in case they get unintentionally mutated during app execution. Even if you plan to mutate your class instances, if you make a copy of an object at the start of a process, you can always compare your “production” class instance to the copy to get a baseline of what happened after changes were made. You may even have a situation that requires you to be able to always return to the original value of the class instance when created, say, in which the original values are default values you use whenever the app starts up.

Remember that value types like structs can be unintentionally changed. While there are less chances for a struct instance to be changed, it still can happen.

Author: Andrew Jaffee

Avid and well-published author, software engineer, designer, and developer, now specializing in iOS mobile app development in Objective-C and Swift, but with a strong background in C#, C++, .NET, JavaScript, HTML, CSS, jQuery, SQL Server, MySQL, Oracle, Agile, Test Driven Development, Git, Continuous Integration, Responsive Web Design, blah, blah, blah ... Did I miss any fad-based catch phrases? My brain avatar was kindly provided by http://icons8.com under a Creative Commons Attribution-NoDerivs 3.0 Unported license.

Leave a Reply

Your email address will not be published. Required fields are marked *