Lesson 2 - First object-oriented app in Swift
In the previous lesson, Introduction to object-oriented programming in Swift, we introduced the object-oriented programming and went over what makes it more efficient than any other programming methodology.
We already know that objects have fields and methods. We also know that to create an object we'd have to create a class first. Remember that a class is a pattern according to which we create instances later.
As we did in the Swift basic constructs course we'll create what must always be done when you encounter a new paradigm in programming, a "Hello World" program. A Hello object world program, to be precise!
We'll start by creating a new console Swift application (Command Line Tool) in Xcode as we're used to. In Project navigator on the left, right-click on our project folder and choose "New File...".
In the dialog, choose "Swift File" and continue by clicking on "Next". We'll
name the new file Greeter.swift
and confirm. We name classes using
CamelCase, meaning that the first letter in every word in the class name is
capitalized and we don't write spaces. The name, of course, shouldn't contain
any accent characters, if your native language uses any, you can use them in
strings, but never in identifiers
We can see a new source file now. In the comment block you can see, for
example, the date of creation. We can also see import Foundation
for importing the Swift basic functionality. We have to declare the class by
ourselves, but it's easy:
class Greeter {
}
We're going to create a greeter
object using this class later,
which will be able to greet the user. You might've noticed by now that we're
treating the program in a different way, for every action, there is some object
responsible. One should not simply start by writing stuff into the
main.swift
file. In our case, it may seem useless, but in more
complex applications it'll prove to be more than worthwhile
In Swift, classes aren't gathered in packages or namespaces, as in some other
programming languages. Instead, the elementary unit is a Swift file, and it's
not necessary to have all the code written within a class. For example, it's a
common practice to create a Constants.swift
file containing just
String
constants declared using let
to have them all
in one place to avoid typo errors.
The second "unit" in Swift are modules. Apple defines them as "single unit of
distribution". A module is simply something that contains the intended
functionality and can be used in various programs. Our new project is actually a
module itself, but it doesn't make sense to use it in other applications.
Similarly, Foundation
is a module, or later UIKit
,
which is imported as a base of iOS applications instead of
Foundation
(because it contains Foundation
).
Next, we'll add a greet()
method to the Greeter
class, which will be publicly visible and won't return a value or take any
parameters.
In Swift, we declare methods as follows:
[access modifier] func [methodName]([parameters]) -> [return type]
We can omit the access modifier before the method, Swift uses
internal
as default, meaning the method can be accessed only from
within the module (which is the whole application in our case). As next, we
write the method's name. We name methods in a similar fashion as variables,
using camelCase, but the very first letter is lowercase. Parentheses with
parameters are required, we'll leave them empty since the method won't have any
parameters. In the method body, we'll write code that prints a message to the
console.
Our class will now look like this:
class Greeter { func greet() { print("Hello object world!") } }
We're finished here for now, let's move to main.swift
.
Now, in the main file, we'll create an instance of the Greeter
class. It'll be the greeter
object which we'll work with further.
We store objects in variables and use the class name as the data type. An
instance typically has the same name as its class, only the very first letter is
lowercase. Let's declare the variable and then create a new instance of the
Greeter
class:
let greeter : Greeter greeter = Greeter()
The first line says: "I want a greeter
variable which will later
contain a Greeter
class instance inside. We've worked with
variables like this before. On the first line, we only make the declaration, so
let
can be used. After the first assignment, it will no longer be
possible to assign a new instance to the greeter
variable.
On the second line, we create a new instance of the Greeter
class using parentheses. We assign this instance to our variable.
When a new instance is created, the constructor is called. The constructor is a special class method, that's why we write the empty parentheses when creating an instance, we're calling this "creation" method. The constructor usually contains some initialization of the object's internal state, e.g. it initializes the fields with default values. We haven't declared a constructor in our class, that's why Swift created the implicit parameterless constructor. So creating an instance of an object is similar to calling a method. Of course, the entire code can be shortened to:
let greeter = Greeter()
Since now we have a Greeter class instance in a variable, we can let it greet
the user. We'll call the greet()
method as
greeter.greet()
. The main.swift
file will now look
like this:
import Foundation let greeter : Greeter greeter = Greeter() greeter.greet()
Let's run the program.
Hello object world!
We've successfully made our first object-oriented app!
Now let's add a name
parameter to our greet()
method, so it could greet the user properly:
func greet(name: String) { print("Hi \(name)!") }
We can see the syntax of the method parameter is the same as the syntax of a
variable. If we wanted more parameters, we'd separate them with commas. Let's
modify our main.swift
file now:
let greeter : Greeter greeter = Greeter() greeter.greet(name: "Carl") greeter.greet(name: "Peter")
Our code is now in a method and we're able to call it multiple times with
different parameters. We don't have to copy "Hi ..." twice. We'll separate our
code logically into methods from now. A big difference between Swift and other
languages is providing the parameter name when calling a method. In our case
name
. The program won't work without it. If we wanted to remove the
parameter names, we'd write _
before the parameter name in the
method declaration. That way we won't have to write the names when calling the
method.
func greet(_ name: String) {
print("Hi \(name)!")
}
Calling the method would then look like this:
greeter.greet("Carl")
The output:
Hi Carl Hi Peter
Let's add some field (attribute) to the class, e.g. a text
where
the greeting will be stored. We declare fields as variables as well. As it was
with methods, if we omit the field's modifier, Swift assumes that it's
iternal
, which we're OK with. Let's modify our class:
class Greeter { var text: String = "" func greet(_ name: String) { print("\(text) \(name)!") } }
We set the text
field to an empty String
.
Otherwise, we'd have to deal with an Optional
or a custom
constructor. We'll now initialize the text of the instance created in
main.swift
:
let greeter = Greeter() greeter.text = "Hi" greeter.greet("Carl") greeter.greet("Peter") greeter.text = "Hello programmer" greeter.greet("Richard")
The output:
Hi Carl Hi Peter Hello programmer Richard
In object-oriented design, it's not recommended to let each object control
the input and output, i.e. printing lots of stuff into the console. Each object
should have a single responsibility and shouldn't exceed its purpose. Let's make
our object solely responsible for creating the greeting text, and we'll move the
printing outside the object, i.e. to the main.swift
file. The
advantage to designing objects with a single responsibility is that they're then
universal and reusable. The object can only output text to the console now, but
we'll change it so the method will only return the text and it'll be up to the
recipient to know what to do with it. We could also store greetings into files,
print them on websites or process them further.
Since we want the method to return a String
value, we'll set the
String
return type to it. We use the
return
keyword to return a value. Return
terminates a method and returns a value. Any code in the method's body after the
return
will not be executed! Let's modify both classes:
The greet()
method in Greeter.swift
:
func greet(_ name: String) -> String { return "\(text) \(name)!" }
The modified code in main.swift
:
let greeter = Greeter() greeter.text = "Hi" print(greeter.greet("Carl")) print(greeter.greet("Peter")) greeter.text = "Hello programmer" print(greeter.greet("Richard"))
Now, our code follows the guidelines of good OOP and over all programming practices.
Great! Our program already has some quality to it, despite it being relatively useless. If you want, you can try to create an object-oriented remake of our console calculator.
In the next lesson, RollingDie in Swift - Constructors and Random numbers, we'll program a simple game. We'll make two objects, warriors, compete in an arena, which will also be an object. See? Now you have something to look forward to!