Swift · · 23 min read

Best Practices for Building Swift Classes

Best Practices for Building Swift Classes

In this tutorial, I’m going to give you some best practices that will help you safely and effectively use classes (reference types) and reference semantics in Swift. Protocol-oriented programming (POP) and value semantics are all the rage now, but a promising new technology doesn’t mean you should throw all your classes away. Why not add some simple constructs to your classes like copy initializers, default initializers, designated initializers, deinitializers, and conformance to the Equatable protocol? To get real about my sample code, I’ll adopt these constructs in some classes that you can use for drawing in your iOS app interfaces.

I’ll walk through the process of creating several protocols, creating classes that adopt those protocols, implement inheritance in these classes, and use instances of the classes (objects), all to illustrate my best practices — and to show some of the extra steps you may have to go through when working with classes.

Using Protocols as much as possible

Defining and using protocols makes your intent crystal clear when defining new classes. When you and other developers look at the signature for your classes, you get meaningful information immediately (like “this class supports a copy constructor”). I also like to confine the requirements of my protocols to one purpose. Since I break my protocols up into focused sets of requirements, my classes can adopt as many or as few protocols as required.

POP, OOP, and POOP?

Remember one very important point: Most code in the iOS (and OS X) SDKs comes in the form of class hierarchies. I believe many of the core frameworks we use are still written in Objective-C (and some C++ and C), like Foundation and UIKit.

Think about creating a new Xcode project based on the iOS Single View App template. Where do many developers get started in this new type of project? Right, in file ViewController.swift. And what do we all see when opening that file? We see ViewController which is a subclass of UIViewController.

What does Apple have to say about ViewController? Here we go:

…You rarely create instances of the UIViewController class directly. Instead, you subclass UIViewController and add the methods and properties needed to manage the view controller’s view hierarchy. …

Every app contains at least one custom subclass of UIViewController. More often, apps contain many custom view controllers. Custom view controllers define the overall behaviors of your app, including the app’s appearance and how it responds to user interactions.

Yeah, yeah, yeah… Apple is pushing protocol-oriented programming (POP), value types (structs and enums), and value semantics. Got it. Heard it loud and clear. But classes (reference types), with their reference semantics and object-oriented programming (OOP) capabilities, will be around for awhile.

There’s always some new programming fad. The only thing I’m concerned about is whether POP is really useful or just another marketing ploy. So far, it has proven useful in my production code, but not all of the time.

The Thing about Classes

Reference types (classes) and reference semantics can be boon or bane, depending on how you use them. But that’s true of all prominently-used technologies. There are advantages and disadvantages. To paraphrase Shakespeare, “To copy or not to copy: that is the question.” My answer to the question is to first understand the problem at hand, trust my intuition, and use copying — value semantics — when necessary, and to not use copying — reference semantics — when necessary.

Even with Apple’s supposed move towards POP, one of the Apple engineers at WWDC 2015 pointed out that “So, for example, a Window. What would it mean to copy a Window?” It wouldn’t mean anything except confusion. Indeed, what would a copy of a UIViewController subclass instance mean and what would you do with it? Entities like UIViewController and NSWindow should remain as classes.

Suppose you used the facade design pattern to create a simple interface and wrapper for a very complex database system that must be used by all customers who’ve bought your app. The most straightforward way to sensibly use this database would be to pass a reference to it around your app. Why in G-d’s name would you write a value type (struct) version of this database and give a copy of the database infrastructure and all its data to each instance of your app? You’d have a disaster.

Obviously, classes still have a place in the software development world. And please don’t think that making a class a member property of a struct is the answer to your prayers. That member property will exhibit reference semantics.

The problem with classes

You all probably know what I’m going to talk about. Here it is from an old Swift blog post:

Copying a reference… implicitly creates a shared instance. After a copy, two variables then refer to a single instance of the data, so modifying data in the second variable also affects the original.”

Here’s an example of the potential perils of reference semantics: I’ll create a class instance, then declare a reference it, then declare another reference to it, set the latter reference equal to the former reference, end up with two references to the same object/instance, change a member property on the latter reference, and see that change reflected in the one instance:

class Coordinate
{
    var x: Float
    var y: Float
    
    init( x: Float, y: Float ) {
        self.x = x
        self.y = y
    }
}

var coordinate = Coordinate(x: 2.0, y: 4.0)
print("coordinate:  (\(coordinate.x), \(coordinate.y))")
// coordinate:  (2.0, 4.0)

// Unintended mutation?
var coordinate1 = coordinate
coordinate1.y = 0.0

print("coordinate:  (\(coordinate.x), \(coordinate.y))")
// coordinate:  (2.0, 0.0)

print("coordinate1: (\(coordinate1.x), \(coordinate1.y))")
// coordinate1: (2.0, 0.0)

coordinate === coordinate1
// true

Output to console from the previous code snippet:

coordinate:  (2.0, 4.0)
coordinate:  (2.0, 0.0)
coordinate1: (2.0, 0.0)

Since objects coordinate and coordinate1 both are references to the object created by the statement Coordinate(x: 2.0, y: 4.0), changes to either reference changes both objects. The statement var coordinate1 = coordinate is an example of copying a reference.

Note my use of the logical === operator:

coordinate === coordinate1
// true

From the Swift docs, === “Returns a Boolean value indicating whether two references point to the same object instance.” Obviously, in this case, coordinate and coordinate1 refer to the same instance. Remember that “Equality is Separate From Identity” and “The identity of a class instance is not part of an instance’s value.”

In even the smallest of apps, I’ve seen errors occur because of “unintended sharing,” unintended mutation — whatever you want to call creating multiple references to the same object and changing a property in one of those references. This behavior can be especially difficult to debug in multi-threaded code involving reference types. But guess what? You can write bad code using value types.

Changes to the first instance of some struct and subsequent changes to copies could still be “unintended.” Any what if you end up with a bunch of copies of a struct? Is it semantically clear as to what your code is doing? Even Apple admits it: “The problem is that immutability has some disadvantages.”

We can get into a discussion of making our classes safe by using Apple’s version of “defensive copying.” where we make all our classes adopt the NSCopying (and perhaps NSObject) protocol(s), and then make sure all our classes implement copy(with:) (mainly just copy()), and remember to call copy() constantly, on every assignment. And calling copy() requires a cast:

let objectCopy = object.copy() as! ObjectType

Be aware that defensive copying is all over Cocoa and Objective-C. There are still many developers who have to support entire legacy iOS and OS X codebases. And there are many of us who must interact with legacy iOS and OS X codebases. We’re not going to take the NSCopying tack in this tutorial. While I will show you some code for defensive copying, I will also show you many other techniques to make your use of classes safe.

Getting Started with a Playground

Let’s create a new Xcode playground. In Xcode, go to File -> New > -> Playground… and click iOS and the Single View template icon. Click the Next button, select a location for your new playground’s bundle, give your playground a name, and click the Create button. Or you can download my sample Xcode playground from GitHub.

Make some room in your playground where my comment “PUT SOME SPACES HERE” is located (shown below) for the class-based code I’m going to write during this tutorial, like so:

//: A UIKit based Playground for presenting user interface
  
import UIKit
import PlaygroundSupport

//
// PUT SOME SPACES HERE
//

class MyViewController : UIViewController {
...

Now replace the “PUT SOME SPACES HERE” comments with the protocol named Copyable and class called Line, as shown below:

//: A UIKit based Playground for presenting user interface
  
import UIKit
import PlaygroundSupport

protocol Copyable: class {
    
    func copy() -> Self
    
}

class Line {
    
    var beginPoint: CGPoint
    var endPoint: CGPoint
    
    init( beginPoint: CGPoint, endPoint: CGPoint ) {
        
        self.beginPoint = CGPoint( x: beginPoint.x, y: beginPoint.y )
        self.endPoint = CGPoint( x: endPoint.x, y: endPoint.y )
        
    }
    
} // end class Line

class MyViewController : UIViewController {
...

You should be able to tell that objects of this class can be used to represent geometric, straight lines by defining beginning points and endpoints. I’ve used the CGPoint class to represent my beginning points and endpoints so that this class is very compatible with drawing/graphics. Currently, the Line class only has a designated initializer.

Best Practices for Classes

Based on the evidence I’ve seen from the evolution of the Swift language, my own experiences with Swift, and observing the experiences of others developing with Swift, classes aren’t going away. OOP features, made possible by use of classes, like inheritance, virtual methods, and polymorphism aren’t going away because they’re just too darn useful.

Let me show you some techniques I use to mitigate “unintended sharing” in classes — and also other approaches you can use just to write better and semantically clear class-based code. A lot of what I’m sharing with you here comes from my years of experience writing C++ code.

Copy constructor (copy initializer)

You may have noticed the Copyable protocol in the initial code I added to our playground. Unintended mutation is one of the biggest critiques of reference semantics, so we’re going to start by mitigating class mutation. Notice my use of Self with a capitalized “S” as opposed to self with a lowercase “s” in my Copyable protocol. This is no accident. From the Swift docs: “Self refers to the eventual type that conforms to the protocol.” If you’re unclear as to what differentiates Self from self, I suggest you study up on the subject.

Why not recognize the “problem” of “unintended sharing” outright and make a semantic statement about it with a protocol that will require conforming classes to implement a “copy constructor?” To climb out of my C++ past, I’ll use Swift terminology and say “copy initializer.” A copy initializer allows us to create an instance of a class using another instance of a class. A new object is created containing the same member properties as another object of the same type. Let me be clear: A fully independent copy of an object is made. Though the new object contains the same data as the original, a new instance (object) is created. The new object is not a reference to the original object. It is a copy and the semantics of the copy initializer makes your intent clear.

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.

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.”

Swift is downright difficult about allowing copying of class instances:

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

In C++, I would override the assignment operator to allow my class to make deep copies of its instances (i.e., their properties’ values) and thus 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.

Let’s make my Line class adopt my Copyable protocol. Copyable requires a conforming class to implement a copy initializer:

protocol Copyable: class {
    
    func copy() -> Self
    
}

class Line : Copyable {

    var beginPoint: CGPoint
    var endPoint: CGPoint
    
    init( beginPoint: CGPoint, endPoint: CGPoint ) {
        
        self.beginPoint = CGPoint( x: beginPoint.x, y: beginPoint.y )
        self.endPoint = CGPoint( x: endPoint.x, y: endPoint.y )
        
    }
    
    func copy() -> Self {
        
        return type(of: self).init( beginPoint: self.beginPoint,
                                    endPoint: self.endPoint )

    }

} // end class Line

I want copy() to instantiate a new instance of my Line class with its current member property values and return that instance. It will do that, but only until I clear up the “Constructing an object of class type ‘Self’ with a metatype value must use a ‘required’ initializer” error message as shown in this image:

Line Design

Remember that Line subclasses will inherit copy() and the method’s implementation is mandated by the Copyable protocol. copy() calls the designated initializer. Note that in the image above, the first character, i, of this init is underlined, telling me that it needs to be marked as required because, according to the official Swift 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…

I marked the designated initializer as required and the code now compiles:

protocol Copyable: class {
    
    func copy() -> Self
    
}

class Line : Copyable {
    
    var beginPoint: CGPoint
    var endPoint: CGPoint
    
    required init( beginPoint: CGPoint, endPoint: CGPoint ) {
        
        self.beginPoint = CGPoint( x: beginPoint.x, y: beginPoint.y )
        self.endPoint = CGPoint( x: endPoint.x, y: endPoint.y )
        
    }
    
    func copy() -> Self {
        
        return type(of: self).init( beginPoint: self.beginPoint,
                                    endPoint: self.endPoint )
        
    }
    
} // end class Line

I wrote some test code. I’ve taken the output shown in the right pane corresponding to each line of code and made it into a comment:

let line1 = Line(beginPoint: CGPoint(x: 20, y: 20), endPoint: CGPoint(x: 20, y: 200))
let lineCopy = line1.copy()
let line2 = line1
line2.beginPoint.x = 80
// line2.beginPoint = {x 80 y 20}
line1.beginPoint
// line1.beginPoint = {x 80 y 20}
lineCopy.beginPoint
// lineCopy.beginPoint = {x 20 y 20}

I initialized an instance of Line and got a reference to it, line1. I got another reference to the Line instance, line2, made and change to one of its properties and, of course, the change is visible in all references.

I used the copy initializer to get an independent copy of my Line instance. That copy is in fact a unique instance. Changing the copy, lineCopy, has no effect on the original instance as referenced by line1 and line2.

To further prove my copy initializer, I compared line1 and line2 with “the triple-equals identical-to operator (===)”. I also compared lineCopy with line1 (and didn’t bother with line2, because it’s identical to line1):

line1 === line2
// true
lineCopy === line1
// false

Is the copy initializer an initializer?
The copy() method you just saw is technically not an initializer, but you 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 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.

Here’s a protocol that indeed requires a by-the-book copy initializer:

protocol Copyable: class
{
    init(copy: Self)
}

While this would be my preferred methodology of requiring and creating copy initializers, I’ve found that it can cause, in my eyes, unwanted and undue complexities when using inheritance. I’m still working on it and will let you know if I get things functioning up to my standards of readability and maintainability.

Inheriting the copy initializer
Created a descendant with a copy initializer can be little tricky, so let’s go ahead and do it to flesh out the concepts required. Let’s extend my Line to include some properties that make it easier to draw the line to screen in an iOS app. I’ll call the descendant DrawableLine. While the following code looks pretty clean, it does have some issues — issues that when resolved will lead to a better understanding of the topics in this tutorial, and for classes in general:

class DrawableLine: Line
{
    
    var color: UIColor
    var width: CGFloat
    
    init( beginPoint: CGPoint, endPoint: CGPoint, color: UIColor, width: CGFloat )
    {
        self.color = color
        self.width = width
        super.init(beginPoint: beginPoint, endPoint: endPoint)
    }
    
    func copy() -> Self
    {
        return type(of: self).init( beginPoint: beginPoint,
                                    endPoint: endPoint,
                                    color: color,
                                    width: width )
    }

} // end class DrawableLine

I need to deal with three problems as described by the Swift compiler: “‘required’ initializer ‘init(beginPoint:endPoint:)’ must be provided by subclass of ‘Line’,” “Overriding declaration requires an ‘override’ keyword,” and “Constructing an object of class type ‘Self’ with a metatype value must use a ‘required’ initializer.” Here’s an image showing the error messages:

ThreeDrawableInheritErrors

I’ll make the required changes and explain them with inline code commentary. Here’s the working Line descendant, DrawableLine:

class DrawableLine: Line
{
    
    var color: UIColor
    var width: CGFloat
    
    // We must implement this init tangentially because of Copyable.
    // The "'required' initializer 'init(beginPoint:endPoint:)' must
    // be provided by subclass of 'Line'" error is then resolved.
    // Think of this as a convenience constructor -- shorthand
    // for rapid prototyping.
    required init( beginPoint: CGPoint, endPoint: CGPoint ) {
        self.color = UIColor.black
        self.width = 1.0
        super.init( beginPoint: beginPoint, endPoint: endPoint )
    }
    
    // Prefxing this init with the "required" keyword resolves the
    // "Constructing an object of class type 'Self' with
    // a metatype value must use a 'required' initializer" error.
    required init( beginPoint: CGPoint, endPoint: CGPoint, color: UIColor, width: CGFloat )
    {
        self.color = color
        self.width = width
        super.init( beginPoint: beginPoint, endPoint: endPoint )
    }
    
    // Prefixing the method with the "override" keyword resolves the
    // "Overriding declaration requires an 'override' keyword" error.
    // We must provide a copy of DrawableLine, not Line.
    override func copy() -> Self
    {
        return type(of: self).init( beginPoint: beginPoint,
                                    endPoint: endPoint,
                                    color: color,
                                    width: width )
    }
    
} // end class DrawableLine

Having to implement the required init(beginPoint:endPoint:) in DrawableLine is a small price to pay for the fact that we can keep creating valid copy initializers for each new subclass of Line we dream up in the future. (And it’s a heck of a lot better than the side effects I’m still wrestling with when using init(copy: Self) and inheritance.)

Regarding the required keyword, remember that when a class adopts a protocol, it must conform to that protocolAND remember that a class can have… what are they called? Descendents. I once again refer you to the section entitled “Required Initializers” in the Swift documentation.

Let me test the DrawableLine copy initializer to make sure that changes to a copy of an instance do not affect that instance. Notice that, just for giggles, I used the initializer upon which I commented that it was a “convenience constructor — shorthand for rapid prototyping.”

let thinBlackLine = DrawableLine(beginPoint: CGPoint(x: 187.5, y: 40.0), endPoint: CGPoint(x: 187.5, y: 300.0))
let thinLineCopy = thinBlackLine.copy()
thinLineCopy.color = UIColor.red
thinBlackLine.color
// UIColor.black
thinBlackLine === thinLineCopy
// false

Drawing my line in a playground
Let’s draw my thinBlackLine to the simulator in the playground. Delete all the boilerplate code at the bottom of the playground starting with this line:

class MyViewController : UIViewController {...

Replace it with the following:

class LineDrawingView: UIView
{
    override func draw(_ rect: CGRect)
    {
        let currGraphicsContext = UIGraphicsGetCurrentContext()
        currGraphicsContext?.setLineWidth(thinBlackLine.width)
        currGraphicsContext?.setStrokeColor(thinBlackLine.color.cgColor)
        // "Begins a new subpath [e.g., line] at the specified point."
        currGraphicsContext?.move(to: thinBlackLine.beginPoint)
        // "Appends a straight line segment from the current point to the specified point."
        currGraphicsContext?.addLine(to: thinBlackLine.endPoint)
        // "Paints a line along the current path."
        currGraphicsContext?.strokePath()
        UIGraphicsEndImageContext()
    }
}

class MyViewController : UIViewController {
    override func loadView() {
        let view = LineDrawingView()
        view.backgroundColor = .white
        self.view = view
    }
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()

If you need help drawing in a playground, click here. Run the playground and you’ll see my line in the “Live View” pane:

ThinBlackLine

Designated initializers

Notice that I used designated initializers in my code samples so far because I’ve got “Class Inheritance and Initialization” on my mind. I suggest you always craft designated initializers unless there are very extenuating circumstances.

When writing classes, I almost always consider the possibility that I might use inheritance to extend those classes at a later time. First, consider that:

Initialization is the process of preparing an instance of a class, structure, or enumeration for use. This process involves setting an initial value for each stored property on that instance and performing any other setup or initialization that is required before the new instance is ready for use.

Some developers skip creating initializers by marking all class member properties as optional. While sometimes it’s necessary to use optional member properties, I try to avoid them as much as possible. Most of the time I find that I can design my classes with non-optional member properties. Swift’s designers knew that having a designated initializer increases a class’s readability, supportability, and potential for future extensibility. It gives you and other developers a lot of insight into your class’s purpose and your intent in writing that class. From the Swift docs:

Designated initializers are the primary initializers for a class. A designated initializer fully initializes all properties introduced by that class and calls an appropriate superclass initializer to continue the initialization process up the superclass chain.

Classes tend to have very few designated initializers, and it is quite common for a class to have only one. Designated initializers are “funnel” points through which initialization takes place, and through which the initialization process continues up the superclass chain.

Every class must have at least one designated initializer. In some cases, this requirement is satisfied by inheriting one or more designated initializers from a superclass…

Default initializers

My next best practice for defining classes comes in the form of another simple protocol:

protocol DefaultInitializable {
    
    init()
    
}

Obviously DefaultInitializable requires adopting classes to implement an init() method, what you should recognize as a default initializer.

Suppose your class contains all optional properties but with no explicit default values. Suppose also you have no default initializer. In other words, all your class’s properties will be nil right after the Swift-provided default initializer is called. Your new class instance (object) could potentially crash your app. I guarantee that no matter what, somebody at some time is going to initialize one of your classes like this:

class Line {
    
    var beginPoint: CGPoint?
    var endPoint: CGPoint?
    
}

let line = Line() // Line
line.beginPoint // nil
line.endPoint // nil

Remember what Swift does in such cases:

Swift provides a default initializer for any structure or class that provides default values for all of its properties and does not provide at least one initializer itself. The default initializer simply creates a new instance with all of its properties set to their default values. …

A strictly optional property with no default value “automatically receives a default value of nil, even though this value is not written in the code.”

So what happens when the developer that uses the previous code snippet passes line, line.beginPoint, or line.endPoint to some complex custom code that doesn’t check for nil? The code crashes.

That’s why DefaultInitializable requires you implement a default initializer (init()). I want you to think through and make semantically clear, in code, the default values your class’s properties start out with when that class first becomes an object.

We’ll look at an example of DefaultInitializable in the next section. I want to show you how you can compose together as many or as few protocols as needed when defining a class.

ARC memory management

When dealing with classes, memory management is handled by ARC and there’s always the possibility for memory leaks. I’m not proposing a magic cure for leaks, but I’m providing you with an aid to track down leaks — or at least know how your instances are behaving memory-wise. Here’s a protocol and extension that I hope you’ll find as useful as I do:

protocol Allocatable: class {
    
    // Give a name to your instance.
    var tag: String { get }
    // Call in initializers.
    func onAllocate()
    // Call in deinitializers.
    func onDeallocate()
    
}

extension Allocatable {
    
    func onAllocate() {
        print("Instance \(tag) of type \(typeIs()) allocated.")
    }
    
    func typeIs() -> String {
        return String(describing: type(of: self))
    }
    
    func onDeallocate() {
        print("Instance \(tag) of type \(typeIs()) deallocated.")
    }
    
} // end extension Allocatable

The tag variable gives you the opportunity to identify individual instances of your classes. I can’t magically conjure up a default initializer for your classes, nor can I require a call to deinit in a protocol. So you have to configure your classes to cooperate with my Allocatable protocol.

Here’s code where I made a parent class adopt both my DefaultInitializable and Allocatable protocols, including some helpful general inline commentary — and comments indicating a few errors I cleaned up as we discussed previously:

protocol Allocatable: class {
    
    // Give a name to your instance.
    var tag: String { get }
    // Call this in initializers.
    func onAllocate()
    // Call this in deinitializers.
    func onDeallocate()
    
}

extension Allocatable {
    
    func onAllocate() {
        print("Instance \(tag) of type \(typeIs()) allocated.")
    }
    
    func typeIs() -> String {
        return String(describing: type(of: self))
    }
    
    func onDeallocate() {
        print("Instance \(tag) of type \(typeIs()) deallocated.")
    }
    
} // end extension Allocatable

class Line: Allocatable, DefaultInitializable {
    
    var beginPoint: CGPoint
    var endPoint: CGPoint
    let tag: String
    
    // "Initializer requirement 'init()' can only be satisfied
    // by a 'required' initializer in non-final class 'Line'"
    // resolved with "required" prefix
    required init() {
        // Defines a straight vertical line in the upper, center
        // of an iPhone 8
        beginPoint = CGPoint( x: 187.5, y: 40.0 )
        endPoint = CGPoint( x: 187.5, y: 300.0 )
        tag = "Untagged"
        
        onAllocate()
    }
    
    init( beginPoint:CGPoint, endPoint:CGPoint, tag: String ) {
        
        self.beginPoint = CGPoint( x: beginPoint.x, y: beginPoint.y )
        self.endPoint = CGPoint( x: endPoint.x, y: endPoint.y )
        self.tag = tag
        
        onAllocate()
    }
    
    deinit {
        onDeallocate()
    }
    
} // end class Line

class DrawableLine: Line {
    
    var color: UIColor
    var width: CGFloat
    
    // "'required' modifier must be present on all overrides of a
    // required initializer" resolved with "required" prefix
    //
    // "Overriding declaration requires an 'override' keyword"
    // resolved when I marked parent init() as "required"
    required init() {
        color = UIColor.black
        width = 1.0
        
        super.init()
    }
    
    required init( beginPoint:CGPoint,
                   endPoint:CGPoint,
                   color: UIColor,
                   width: CGFloat,
                   tag: String ) {
        
        self.color = color
        self.width = width
        
        super.init( beginPoint: beginPoint, endPoint: endPoint, tag: tag )
    }
    
} // end class DrawableLine

The next code snippet shows you how my DefaultInitializable protocol encouraged safe usage of class instances created only using their default initializers (look at line and drawableLine3). It also shows you how the designated initializer allowed me to create two fully-configured lines, drawableLine1 and drawableLine2:

let line = Line()
let drawableLine1 = DrawableLine(beginPoint: CGPoint(x: 40.0, y: 40.0), endPoint: CGPoint(x: 40.0, y: 300.0), color: UIColor.red, width: 8.0, tag: "Line 1")
let drawableLine2 = DrawableLine(beginPoint: CGPoint(x: 40.0, y: 300.0), endPoint: CGPoint(x: 300.0, y: 300.0), color: UIColor.red, width: 8.0, tag: "Line 2")
let drawableLine3 = DrawableLine()

Then I wrote code to render those lines in a CGContext

class LineDrawingView: UIView
{
    override func draw(_ rect: CGRect)
    {
        let currGraphicsContext = UIGraphicsGetCurrentContext()
        
        currGraphicsContext?.setLineWidth(drawableLine1.width)
        currGraphicsContext?.setStrokeColor(drawableLine1.color.cgColor)
        // "Begins a new subpath [e.g., line] at the specified point."
        currGraphicsContext?.move(to: drawableLine1.beginPoint)
        // "Appends a straight line segment from the current point to the specified point."
        currGraphicsContext?.addLine(to: drawableLine1.endPoint)
        // "Paints a line along the current path."
        currGraphicsContext?.strokePath()
        
        currGraphicsContext?.setLineWidth(drawableLine2.width)
        currGraphicsContext?.setStrokeColor(drawableLine2.color.cgColor)
        // "Begins a new subpath [e.g., line] at the specified point."
        currGraphicsContext?.move(to: drawableLine2.beginPoint)
        // "Appends a straight line segment from the current point to the specified point."
        currGraphicsContext?.addLine(to: drawableLine2.endPoint)
        // "Paints a line along the current path."
        currGraphicsContext?.strokePath()

        currGraphicsContext?.setLineWidth(drawableLine3.width)
        currGraphicsContext?.setStrokeColor(drawableLine3.color.cgColor)
        // "Begins a new subpath [e.g., line] at the specified point."
        currGraphicsContext?.move(to: drawableLine3.beginPoint)
        // "Appends a straight line segment from the current point to the specified point."
        currGraphicsContext?.addLine(to: drawableLine3.endPoint)
        // "Paints a line along the current path."
        currGraphicsContext?.strokePath()

        UIGraphicsEndImageContext()
    }
}

class MyViewController : UIViewController {
    override func loadView() {
        let view = LineDrawingView()
        view.backgroundColor = .white
        self.view = view
    }
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()

… and displayed those lines in the Simulator:

Red angle with black line

The following sample code will show you how, using local scope in a playground, my Allocatable protocol allows you to track ARC memory allocation and deallocation:

do {
    let line = Line()
    let drawableLine1 = DrawableLine(beginPoint: CGPoint(x: 40.0, y: 40.0), endPoint: CGPoint(x: 40.0, y: 300.0), color: UIColor.red, width: 8.0, tag: "Line 1")
    let drawableLine2 = DrawableLine(beginPoint: CGPoint(x: 40.0, y: 300.0), endPoint: CGPoint(x: 300.0, y: 300.0), color: UIColor.red, width: 8.0, tag: "Line 2")
    let drawableLine3 = DrawableLine()
}

Here is the playground output to console:

Instance Untagged of type Line allocated.
Instance Line 1 of type DrawableLine allocated.
Instance Line 2 of type DrawableLine allocated.
Instance Untagged of type DrawableLine allocated.
Instance Untagged of type DrawableLine deallocated.
Instance Line 2 of type DrawableLine deallocated.
Instance Line 1 of type DrawableLine deallocated.
Instance Untagged of type Line deallocated.

Conformance to Equatable

You should always be able to determine if the properties of one instance of a class are the same as, or different than, those of another instance, especially because of the possibility of unintended mutation.

First of all, remember that you can always definitively determine if several different references of the same type refer to the same instance (object) using the “identical-to operator,” more commonly known as ===.

But suppose you’ve used your copy initializer to save a baseline of the values of you used to create the original object. Suppose later you want to see how far the original object’s values have strayed from their baseline values.

Or consider that you’ve got many different instances of classes and you need the ability to compare their property values for some business requirement (e.g., to find a last name match in a group of object’s representing persons). Equatable is an essential protocol (tool) to have. If you don’t remember Equatable, see my explanation here and Apple’s here.

So I added Equatable conformance to my Line class (which already conforms to Allocatable and DefaultInitializable). I kept it simple when determining whether Line instances were equal (and note that Equatable provides !=). I used the Pythagorean Theorem (see here and here) to determine and compare the length of two lines. Here’s the new Line code, which is inherited by DrawableLine:

class Line: Allocatable, DefaultInitializable, Equatable {
    
    var beginPoint: CGPoint
    var endPoint: CGPoint
    let tag: String
    
    // "Initializer requirement 'init()' can only be satisfied
    // by a 'required' initializer in non-final class 'Line'"
    // resolved with "required" prefix
    required init() {
        // Defines a straight vertical line in the upper, center
        // of an iPhone 8
        beginPoint = CGPoint( x: 187.5, y: 40.0 )
        endPoint = CGPoint( x: 187.5, y: 300.0 )
        tag = "Untagged"
        
        onAllocate()
    }
    
    init( beginPoint:CGPoint, endPoint:CGPoint, tag: String ) {
        
        self.beginPoint = CGPoint( x: beginPoint.x, y: beginPoint.y )
        self.endPoint = CGPoint( x: endPoint.x, y: endPoint.y )
        self.tag = tag
        
        onAllocate()
    }
    
    // The line length formula is based on the Pythagorean theorem.
    func length () -> CGFloat
    {
        let length = sqrt( pow(endPoint.x - beginPoint.x, 2) + pow(endPoint.y - beginPoint.y, 2) )
        return length
    }
    
    static func == ( lhs: Line, rhs: Line ) -> Bool {
        return (lhs.length() == rhs.length())
    }

    deinit {
        onDeallocate()
    }
    
} // end class Line

Here’s some code to test my Equatable implementation, including the playground’s evaluation of each statement in the right-hand pane which I copied into comments next to each line below:

drawableLine1 == drawableLine3 // true
drawableLine1 != drawableLine3 // false
drawableLine1 == drawableLine2 // true

Look at the image above and the values I used in initializers in a previous code snippet above to compare and validate line lengths and results.

Conclusion

No matter what paradigm you’re using, you need to have a set of best practices. Anybody can take a good tool and screw it all up. Successful developers get to know a technology like OOP through study and practice, practice, practice — writing lots of code. They’re willing to learn from their mistakes and admit when they’re wrong. They’re also willing to listen to mentors and/or the collective wisdom of their peers and adopt best practices.

I’ve presented you with some best practices for classes using constructs like copy initializers, default initializers, designated initializers, deinitializers, and conformance to the Equatable protocol. This is not an exhaustive list, for example, I could’ve covered failable initializers (init?()), but can’t cover everything in one article. I hope they help you. At least try them out. You can find an enormous amount of advice on OOP out there, even for a relatively new language like Swift.

Get out out there and experiment, practice, study, and be your best!

Read next