قالب وردپرس درنا توس
Home / IOS Development / Best Practices for Building Swift Classes

Best Practices for Building Swift Classes



In this tutorial, I will provide you with some great methods that will help you to use classes (reference types) and reference sants in Swift in a safe and efficient way. Protocol-oriented programming (POP) and value mentality are all the rage now, but a promising new technology does not mean you should throw away all your classes. Why not add some simple constructions to your classes, such as copy initiators, default initializers, designated initiatives, deinitializers and compliance with the Equatable protocol? To get real about my test, I will adopt these constructions in some classes that you can use to draw in your iOS interfaces.

I'm going through the process of creating multiple protocols, creating classes adopting these protocols, implementing legacy in these classes, and using instances of the classes (objects), all to illustrate my best practices ̵

1; and to show some of The extra steps you need to go through when working with classes.

Use protocols as much as possible

Define and use protocols make your purpose crystal clear when defining new classes. When you and other developers look at the signature of your classes, you get meaningful information immediately (like "this class supports a copy designer"). I also like to limit the requirements of my protocols for one purpose. Since I break my protocols into focused requirements, my classes can adopt as many or as few protocols as necessary.

POP, OOP and POOP?

Remember a very important point: Most code in IOS and OS X) SDKs come in the form of class hierarchies. I think that many of the core works we use are still written in Objective-C (and some C ++ and C), like the Foundation and UIKit .

Think about creating a new Xcode project based on iOS Single View App template. Where do many developers start in this new type of project? Right, in file ViewController.swift . And what do we see when you open 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 are subclass UIViewController and add the methods and properties needed to manage the viewer's display hierarchy. …

Each app contains at least one custom subclass of UIViewController . More often, apps contain many custom viewers. Custom View Controls define the overall behavior of your app, including the app's appearance and how it reacts to user interactions.

Yes, yes … Apple pushes protocol-oriented programming (POP), value types (structures and enums), and values ​​semantics. Have it. Heard loud and clear. However, classes (reference types), with reference demantics and object-oriented programming (OOP), will exist for a while.

There is always something new programming dish. All I'm concerned about is POP's really useful or just another marketing key. So far, it has proven to be useful in my production code, but not all the time

.

The Thing About Classes

Reference Types (classes) and Reference Anticipation may be boon or orbit, depending on how you use them. But it is true for all prominent used technologies. There are advantages and disadvantages. To rewrite Shakespeare, "To copy or not copy: That's the question." My answer to the question is to first understand the problem at hand, rely on my intuition, and use copying – value semantics – when necessary and do not use copying – reference probes – when needed.

Even with Apple's alleged motion against POP, one of Apple engineers at WWDC 2015 pointed out that "For example, a window. What would it mean to copy a window?" That would not mean anything but confusion. In fact, what would a copy of a UIViewController subclass instance mean and what would you do with it? Devices like UIViewController and NSWindow should remain as classes.

Suppose you used the facade design pattern to create a simple interface and wrapping for a highly complex database system that may be used by all customers who purchased your app. The easiest way to make sense using this database would be to send a reference to the one around your app. Why in G-d's name would you write a value-type (struct) version of this database and provide a copy of the database's infrastructure and all the data for each instance of your app? You want a disaster.

Of course, classes still have a place in the software development world. And please do not believe that making a class a member of a structure is the answer to your prayers. That membership will show reference semantics.

The problem with classes

You all know for sure what to talk about. Here's from an old Swift blog post:

Copying a reference … implicitly creates a shared instance. After a copy, two variables refer to a single instance of the data so that changing data in the other variable also affects the original. "

Here is an example of the potential dangers of reference antics: I will make a class case, then declare a reference, and declare another reference to it, see the latter reference similar to the previous reference, ending with two references to the same object / instance, change a membership hub on the latter reference, and see that change reflected in one instance:

)

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

Note my use of the logical === operator:

From Swift docs, === "Returns a Boolean value that indicates whether two references point to the same object instance. "It is obvious that in this case coordinate and coordinate1 refer to the same example. Remember, "Equality is separate from identity" and "Identity of a class example is not part of an instance's value."

In even the smallest apps I've seen errors occurring due to "accidental sharing", accidental mutation – no matter what you want to call create more references to the same object and change a property in one of these references. This behavior can be particularly difficult to troubleshoot in multi-threaded code that involves referencing types. But guess what? You can write bad code using value types.

Changes in the first instance of some structures and subsequent changes to copies may still be "unintended". What if you end up with a variety of copies of a structure? Is it semantically clear what your code does? Even Apple admit it: "The problem is that immutability has some disadvantages."

We can come into discussion about making our classes safe by using Apple's version of "Defensive Copy." Where we make all our classes adopt NSCopying (and maybe NSObject ) protocol (s), make sure all our classes implement copy (with :) ( mainly only copy ) and remember to call copy () all the time on each assignment. And calling copy () requires a cast:

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 initit 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:

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:

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" width="1940" height="720" class="aligncenter size-full wp-image-14057" srcset="https://www.appcoda.com/wp-content/uploads/2018/09/ThreeDrawableInheritErrors.png 1940w, https://www.appcoda.com/wp-content/uploads/2018/09/ThreeDrawableInheritErrors-200x74.png 200w, https://www.appcoda.com/wp-content/uploads/2018/09/ThreeDrawableInheritErrors-600x223.png 600w, https://www.appcoda.com/wp-content/uploads/2018/09/ThreeDrawableInheritErrors-768x285.png 768w, https://www.appcoda.com/wp-content/uploads/2018/09/ThreeDrawableInheritErrors-1024x380.png 1024w, https://www.appcoda.com/wp-content/uploads/2018/09/ThreeDrawableInheritErrors-1240x460.png 1240w, https://www.appcoda.com/wp-content/uploads/2018/09/ThreeDrawableInheritErrors-860x319.png 860w, https://www.appcoda.com/wp-content/uploads/2018/09/ThreeDrawableInheritErrors-680x252.png 680w, https://www.appcoda.com/wp-content/uploads/2018/09/ThreeDrawableInheritErrors-400x148.png 400w, https://www.appcoda.com/wp-content/uploads/2018/09/ThreeDrawableInheritErrors-50x19.png 50w" sizes="(max-width: 1940px) 100vw, 1940px"/></div>
<p>I’ll make the required changes and explain them with inline code commentary. Here’s the working <code>Line</code> descendant, <code>DrawableLine</code>:</p>
<div id=

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

class DrawableLine: Line

{

    

    var color: UIColor

    var width: CGFloat

    

    // We must implement this init tangentially because of Copyable.

    // The "&#39;required&#39; initializer &#39;init(beginPoint:endPoint:)&#39; must

    // be provided by subclass of &#39;Line&#39;" 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 &#39;Self&#39; with

    // a metatype value must use a &#39;required&#39; 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 &#39;override&#39; 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.”

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:

Replace it with the following:

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" width="776" height="1118" class="aligncenter size-full wp-image-14060" srcset="https://www.appcoda.com/wp-content/uploads/2018/09/ThinBlackLine.png 776w, https://www.appcoda.com/wp-content/uploads/2018/09/ThinBlackLine-200x288.png 200w, https://www.appcoda.com/wp-content/uploads/2018/09/ThinBlackLine-208x300.png 208w, https://www.appcoda.com/wp-content/uploads/2018/09/ThinBlackLine-768x1106.png 768w, https://www.appcoda.com/wp-content/uploads/2018/09/ThinBlackLine-711x1024.png 711w, https://www.appcoda.com/wp-content/uploads/2018/09/ThinBlackLine-680x980.png 680w, https://www.appcoda.com/wp-content/uploads/2018/09/ThinBlackLine-400x576.png 400w, https://www.appcoda.com/wp-content/uploads/2018/09/ThinBlackLine-50x72.png 50w" sizes="(max-width: 776px) 100vw, 776px"/></div>
<h3>Designated initializers</h3>
<p>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.</p>
<p><strong>When writing classes, I almost always consider the possibility that I might use inheritance to extend those classes at a later time.</strong> First, consider that:</p>
<blockquote>
<p>
<em>Initialization</em> 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.
</p>
</blockquote>
<p>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:</p>
<blockquote>
<p>
<em>Designated initializers</em> 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.</p>
<p> 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.</p>
<p> 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…
</p>
</blockquote>
<h3>Default initializers</h3>
<p>My next best practice for defining classes comes in the form of another simple protocol:</p>
<div id=

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:

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 nileven though this value is not written in the code.”

So what happens when the developer that uses the previous code snippet passes lineline.beginPointor 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:

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:

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:

Then I wrote code to render those lines in a CGContext

… 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:

Here is the playground output to console:

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 Equatablesee 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:

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:

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!


Source link