In this tutorial we cover the following topics
- General notes about structures and classes in Swift
- Simple structures and classes definition
- Properties
- Methods (instance and type methods)
- Mutating methods
- Subscripts
- Inheritance
Both structures and classes in Swift can
- define properties to store values;
- define methods to provide functionality;
- define initializesr to set up their initial state;
- conform to protocols to provide standard functionality of a certain kind;
- define subscripts to provide access to their values using subscript syntax;
- be extended to expand their functionality beyond a defaut implementation.
Moreover, classes have some additional apapbilities that structures do not
- inheritance;
- we can check and interprete the type of a class instance at runtime;
- reference counting allows more than one reference to a class instance;
- deinitializers enable an instance of a class to free up any resorces it has assigned.
Another worth mention difference is that structures ale always copied when they are passed around in the code and to not use reference counting. Structure instances are always passed by value, and class instances are always passed by reference.
Objective-C vs. Swift difference
In Onjective-C's Foundation
NSArray
, NSDictionary
, NSString
are implemented as classes, not structures. This means that arrays, dictionaries and strings are always passed as a references, rather than as a copy.
In Swift Array
, Dictionary
and String
are implemented as structures. This means that data of this type when are assigned to a new constant or variable, or when they are passed to a function or method, are copied.
Properties initialization is Swift is a little bit tricky. For classes we cannot leave properties uninitialized, for example
1 |
var integrePropertie: Int |
is not allowed and cause and error: Class 'FooClass' has no initializers
. Because for structures we have automatically generated memberwise initializer, we have to use it to initialize the member properties of new structure instances if they are not initialized explicitely. For classes we have to use explicite statement like in var integreVariableStoredPropertie = 0
or initializers
(see further).
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 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
import Foundation // For classes we cannot leave properties uninitialized, for example // var integreProperty: Int // is not allowed and cause and error: Class 'FooClass' has no initializers // Simple class definition class FooClass { var integerVariableStoredProperty = 0 var stringVariableStoredProperty: String? let doubleConstantStoredProperty = 0.0 } // Simple structure definition struct FooStruct { var integerVariableStoredProperty: Int var stringVariableStoredProperty: String let doubleConstantStoredProperty: Double } struct BarStruct { var integerVariableStoredProperty = 3 var stringVariableStoredProperty = "Some bar struct string" let doubleConstantStoredProperty = 3.3 } var instanceFooClass_1 = FooClass() let instanceFooClass_2 = FooClass() // Have to use automatically generated memberwise initializer var instanceFooStruct_1 = FooStruct(integerVariableStoredProperty: 1, stringVariableStoredProperty: "Some struct 1 string", doubleConstantStoredProperty: 1.1) let instanceFooStruct_2 = FooStruct(integerVariableStoredProperty: 2, stringVariableStoredProperty: "Some struct 2 string", doubleConstantStoredProperty: 2.2) // Cannot use automatically generated memberwise initializer var instanceBarStruct = BarStruct() instanceFooClass_1.integerVariableStoredProperty = 1 instanceFooClass_1.stringVariableStoredProperty = "Some foo class string" // Cannot assign to constant //instanceFooClass_1.doubleConstantStoredProperty = 1.1 // Even though that instanceFooClass_2 is constant, we can modify its properties. instanceFooClass_2.integerVariableStoredProperty = 2 instanceFooClass_2.stringVariableStoredProperty = "Other foo class string" instanceFooStruct_1.integerVariableStoredProperty = 2 instanceFooStruct_1.stringVariableStoredProperty = "New string for struct 1" //instanceFooStruct_1.doubleConstantStoredProperty = 2.2 // Cannot assign to properties of a constant structure //instanceFooStruct_2.integerVariableStoredProperty = 2 //instanceFooStruct_2.stringVariableStoredProperty = "New string for struct 1" |
Notice that even though that instanceFooClass_2
is constant, we can modify its properties. We cannot do this with structures. Explanation of this is simple: first uses references (and this reference stays constant) while latter uses real values.
Classes, structures and enumerations can define computed properties. This type of properties do not actually store a value. Instead, they provide a getter (
get
) and an optional setter (set
) to retrieve and set other properties and values indirectly. Sometimes we don't need to store explicitly (permanently) some value - for example it may be to expensive (taking into consideration memory usage) or this value may be computed based on other values.
To define computed property we use get
and set
keyword. In case we want to have a read only computed property we can skip the get
keyword (and of course there is no set
because this is read only property).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
struct StructWithComputedProperty { var simpleProperty = 3 var computedProperty: Int { get { return simpleProperty*2 } set(newValue) { simpleProperty = newValue*3 } } var computedPropertyReadOnly: Int { return simpleProperty*3 } } var swcp = StructWithComputedProperty() print("simpleProperty=\(swcp.simpleProperty) computedProperty=\(swcp.computedProperty) computedPropertyReadOnly=\(swcp.computedPropertyReadOnly)") swcp.computedProperty = 5 print("simpleProperty=\(swcp.simpleProperty) computedProperty=\(swcp.computedProperty) computedPropertyReadOnly=\(swcp.computedPropertyReadOnly)") |
simpleProperty=3 computedProperty=6 computedPropertyReadOnly=9
simpleProperty=15 computedProperty=30 computedPropertyReadOnly=45
Another new concept in Swift is a lazy stored property. A lazy stored property is a property whose initial value is not calculated until the first time it is used. Lazy properties may be useful when the initial value for a property is not known until after an instance's initizlization is complete, for example we may use it for time consuming initialization process. Lazy property must always be declared as a variable - constant properties must always have a value before initialization completes.
In the following code, we have to use init
nad func
- dont't worry about them now, take them as they are; we will discuss them in the next part of the material. First one is a special function called when an instance is created, second defines a function (an action) we can call on this instance (we can perform on this instance)
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 |
print("=== Lazy stored property ===") class Action { init() { print("Init Action class") } func doSomething(){ print("Do something for Action instance") } } class LazyAction { init() { print("Init LazyAction class") } func doSomething(){ print("Do something for LazyAction instance") } } class ActionExecutor { var action = Action() lazy var lazyAction = LazyAction() func executeAction(){ action.doSomething() lazyAction.doSomething() } } var ae = ActionExecutor() ae.executeAction() |
Init Action class
Do something for Action instance
Init LazyAction class
Do something for LazyAction instance
If a property marked as a
lazy
is accessed by multiple threads simultaneously and the property has not yet been initialized, there is no guarantee that the property will be initialized only once.
Property observers are really great things. They monitor to any changes in a property's value. Every time a property's value is set, even if the new value is not really new, observer is called. We can add property observer to any stored properties, except for lazy stored properties.
Thera are two observers.
- First,
willSet
is called just before the value is stored. This observer gets new property value as a constant parameter with a default name ofnewValue
. We can specify our own name if we don't like this one. - Second,
didSet
is called just after the new value is stored. This observer gets old (previous)) property value as a constant parameter with a default name ofoldValue
. We can specify our own name if we don't like this one.
When a default value is assigned to a stored property, or its initial value is set with an initializer, the value of that property is set directly, without calling any property observers.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
struct StructWithPropertyObservers { var firstPropertyToObserve:Int = 3 { willSet{ print("firstPropertyToObserve is going to get new value of: \(newValue)") } didSet{ print("An old value of firstPropertyToObserve (\(oldValue)) has just been replaced by a new one") } } var secondPropertyToObserve:Int = 7 { willSet(newValueToBeSet){ print("secondPropertyToObserve is going to get new value of: \(newValueToBeSet)") } didSet(oldValueToBeReplaced){ print("An old value of secondPropertyToObserve (\(oldValueToBeReplaced)) has just been replaced by a new one") } } } var swpo = StructWithPropertyObservers() swpo.firstPropertyToObserve = 5 swpo.secondPropertyToObserve = 9 |
firstPropertyToObserve is going to get new value of: 5
An old value of firstPropertyToObserve (3) has just been replaced by a new one
secondPropertyToObserve is going to get new value of: 9
An old value of secondPropertyToObserve (7) has just been replaced by a new one
Type properties are properties related with particular type rather than particular instance of that type. They are like static variables or constants in C or Java.
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 |
struct StructWithTypeProperty { static var storedTypeProperty = 11 static var computedTypeProperty: Int { return storedTypeProperty * 4 } } class ClassWithTypeProperty { static var storedTypeProperty = 22 static var computedTypeProperty: Int { return storedTypeProperty * 3 } // With 'class' keyword a subclass is allowed to override // the superclass's implementation. // Can't do the following //class var overridableStoredTypeProperty = 22 class var overridableComputedTypeProperty: Int { return storedTypeProperty * 5 } } enum EnumerationWithTypeProperty { static var storedTypeProperty = 33 static var computedTypeProperty: Int { return storedTypeProperty * 2 } } print("\(StructWithTypeProperty.computedTypeProperty) \(ClassWithTypeProperty.computedTypeProperty) \(EnumerationWithTypeProperty.computedTypeProperty)") StructWithTypeProperty.storedTypeProperty = 111 ClassWithTypeProperty.storedTypeProperty = 222 EnumerationWithTypeProperty.storedTypeProperty = 333 print("\(StructWithTypeProperty.computedTypeProperty) \(ClassWithTypeProperty.computedTypeProperty) \(EnumerationWithTypeProperty.computedTypeProperty)") |
44 66 66
444 666 666
- Instance methods are functions which esence of existence is to provide some functionality related to the instance of a class. In other words, this type of functions works on particular instance of a class. So first we have to create on object and then we can perform some actions on it using instance methods. Instance methods have exactly the same syntax as functions.
- Type methods, in contrast to instance methods, are functions which esence of existence is to provide some functionality related to the type itself rather than instance of a class. In other words, this type of functions works on a class. So we don't have to create any object of a class to use this class type methods.
As in many modern programming languages, we don't have to write self
, which is used to refer to the current instance within its own methods, when it is not realy needed.
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 |
class ClassWithMethods { static var multiplayer = 1 static func setMultiplayer(to value: Int){ multiplayer = value } var property = 0 func resetProperty(){ property = 0 } func changeProperty(to value:Int){ // In most cases we don't have to write 'self' //self.property = value * ClassWithMethods.multiplayer property = value * ClassWithMethods.multiplayer } func printProperty(){ print(property) } } var cwm = ClassWithMethods() cwm.printProperty() cwm.changeProperty(to: 12) cwm.printProperty() cwm.resetProperty() cwm.printProperty() ClassWithMethods.setMultiplayer(to: 3) cwm.changeProperty(to: 12) cwm.printProperty() |
0
12
0
36
Notice that in the above example instead of static func
we could also write class func
as in the example below
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
class ClassA { static func func1() { print("func1") } class func func2() { print("func2") } // Same as static func final class func func3() { print("func3") } } let classA = ClassA() ClassA.func1() ClassA.func2() ClassA.func3() |
func1
func2
func3
As we can see both seems to be the same. They seems to be, but they are not the same. Simplyfying, we can say that
static
is for static functions of structs and enums, and class
for classes and protocols. Because classes, but not structs, can inherit from another class, class functions have other properties: they can be overridden by subclasses and they are dynamically dispatched.
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 |
class ClassA { static func func1() { print("func1") } class func func2() { print("func2") } // Same as static func final class func func3() { print("func3") } } class ClassB : ClassA { override class func func2() { print("func2 in ClassB") } // Error: Cannot override static method /* override static func func1() { print("func2 in ClassB") } */ } let classB = ClassB() ClassB.func1() ClassB.func2() ClassB.func3() |
func1
func2 in ClassB
func3
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
class MyClass { class func foo() { print("myClass") } } class MyOtherClass: MyClass { override class func foo() { print("myOtherClass") } } var x: MyClass = MyOtherClass() type(of: x).foo() x = MyClass() type(of: x).foo() |
myOtherClass
myClass
For structures and enumeration we can also implement mutating methods which allows to modify the properties of value type (structures and enumerations but not classes which are reference type) from within its instance methods. By default, the properties of a value type cannot be modified from within its instance methods.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// Structures and enumerations are value types. By default, the properties // of a value type cannot be modified from within its instance methods. struct Item { var item: String var priority: Int func nonMutate() -> String { return item } mutating func mutate(_ value: String) { item = value } } var i = Item(item: "Test string", priority: 1) print("Item \(i.item) has priority \(i.priority)") i.item = "Another text" i.mutate("Again new text") print(i.nonMutate()) |
Item Test string has priority 1
Again new text
This ia another feature we can find in Swift - maybe not really necessary, but with it our life can be simpler. We can define subscripts for structures, classes and enumeration. Typically subscripts are used for arrays and dictionaries and are denoted with square brackets
[
and ]
. We can use this syntax to have more natural access to some of our properties.
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 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
class uselessClass { var int:Int = 0 var string:String = "" var bool:Bool = false var string2:String = "" subscript (index: Int) -> Int { get{ print("First subscript, get, \(index)") return int } set(new){ print("First subscript, set, \(index)=\(new)") print(new) int = new } } subscript (index: Int) -> String { get{ print("Second subscript, get, \(index)") return string } set(new){ print("Second subscript, set, \(index) = \(new)") print(new) string = new } } subscript (numeralIndex code: Int, stringIndex password: String) -> Bool { get{ print("Third subscript, get, \(code) + \(password)") return bool } set(new){ print("Third subscript, set, \(code) + \(password) = \(new)") print(new) bool = new } } subscript (code: Int, password: String) -> String { get{ print("Fourth subscript, get, \(code) + \(password)") return string2 } set(new){ print("Fourth subscript, set, \(code) + \(password) = \(new)") print(new) string2 = new } } } var uc = uselessClass() uc[2] = 3 uc[2] = "new string" uc[numeralIndex: 2222, stringIndex: "SeCrEt"] = true uc[3333, "PaSSworD"] = "Secret message" var ansInteger:Int = uc[2] print(ansInteger) var ansString:String = uc[2] print(ansString) print(uc[numeralIndex: 2222, stringIndex: "SeCrEt"]) print(uc[3333, "PaSSworD"]) |
First subscript, set, 2=3
3
Second subscript, set, 2 = new string
new string
Third subscript, set, 2222 + SeCrEt = true
true
Fourth subscript, set, 3333 + PaSSworD = Secret message
Secret message
First subscript, get, 2
3
Second subscript, get, 2
new string
Third subscript, get, 2222 + SeCrEt
true
Fourth subscript, get, 3333 + PaSSworD
Secret message
I hope that you have an understanding what is an inheritance, so I'll give only syntax information.
1 2 3 |
class Subclass: Superclass { // Subclass definition goes here } |
We use override
keyword to mark method, property or subscript as overriden. Superclass's method, property or subscript is accessed with super
keyword. To prevent method, property or subscript from being overriden we mark it with final
modifier. We can mark entire class as final preventing them to be subclassing.
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 40 41 42 43 |
class baseClass { var somePropertyInt: Int init (){ print("baseClass: init"); somePropertyInt = 5 } func doSomething(){ print("baseClass: doSomething") } final func notForSubclassing(){ print("baseClass: notForSubclassing") } func anotherOneFunction(){ print("baseClass: anotherOneFunction") } } class subClass: baseClass { var somePropertyString: String override init (){ print("subClass: init"); somePropertyString = "text" } override func doSomething(){ print("subClass: doSomething") } func callMethodFromSuperClass() { print("subClass: callMethodFromSuperClass") super.notForSubclassing() } } var sc = subClass() sc.doSomething() sc.notForSubclassing() sc.anotherOneFunction() print("\(sc.somePropertyInt) \(sc.somePropertyString)") sc.callMethodFromSuperClass() |
subClass: init
baseClass: init
subClass: doSomething
baseClass: notForSubclassing
baseClass: anotherOneFunction
5 text
subClass: callMethodFromSuperClass
baseClass: notForSubclassing