Swift Protocols, Structs and Classes

Note: I’m going through draft posts that go back to 2014 and publishing where they still may have value. They may not be 100% upto date but better published late than never.

Introduction

Protocols can be thought of as similar to interfaces within languages such as C# and Java, hence are used to declare expectations for implementations and can be returned or accepted as params.

We declare a protocol in the following way

protocol Person {
  var firstName: String { get }
  var lastName: String { get }
  var age: Int { get }
}

So we can create our implementation of this procotol like this

struct PersonImpl : Person {
  var firstName: String
  var lastName: String
  var age: Int
}

Structs and Classes

Apple Swift developers seem to prefer value types over reference types, a struct is a value type and class a reference type. They both share many of the same functionality.

Structs do not require an initializer (constructor) as a default initializer is supplied automatically and will expect all non-optional properties to be supplied.

class PersonImpl : Person {
  var firstName: String
  var lastName: String
  var age: Int

  init(firstName: String, lastName: String, age: Int) {
    self.firstName = firstName
    self.lastName = lastName
    self.age = age
  }
}

As you can see, init is the name of the initializer or what we may view as a constructor. Classes may also have deinit is called when Swift deallocates an instance (struct’s do not have deinit).

Mutations

Where we expect changes to the internal data, in other words where a function mutates data we use the mutating keyword. If, for example, we add a protocol function

func incrementAge() -> Void // same as func incrementAge()

By default struct properties cannot be changed by instance method. In such cases we need to include the mutating key word, i.e.

mutating func incrementAge() -> Void

So a struct method implementation will now look like this

mutating func incrementAge() -> Void {
  age += 1
}

Within a class, we don’t need to mark the method as mutating.

Accessors

There are five access levels

  • Open: This allows proprties, methods, classes etc. to be accessible by anything that imports the module
  • Public: This allows proprties, methods, classes etc. to be accessible by anything that imports the module
  • Internal: This is the default access level and allows properties, methods, classes etc. to be accessible from the module where they’re defined only
  • Fileprivate: Properties and methods can access from code withint he same source file only
  • Private: The least visible accessor meaning properties methods etc. are only visible within the same file or class etc.

is and as

Swift has the concept of is and as pretty much like C#. So we can declare a type as follows

let p: Person = PersonImp("Scooby", "Doo", "12")

Now if we needed to check if the const p is a PersonImpl we would use

if p is PersonImpl {    
}

we could use as within the if statement if we wanted as well, for example

if let person = p as? PersonImpl {    
}

Here we compare and assign in the same line of code using as? which basically means the person may be nil (if the type cannot be converted to PersonImpl).

Extensions

Just like extension classes in C# – Swift extensions allow us to add functionality to structures, protocols and classes (as well as enumerations).

An extension is defined using the extension key word, for example

extension String {
  // add string functionality, for example
  func capitalize() -> String {
    return self[0].toUpper() + self.substr(1)
  }
}

We can also add constraints to extensions, so for example a Collection extension that

extension Collection where Iterator.Element: Comparable {
  // add functionality where elements support Comparable
}

Equatable

A type needs to conform to the Equatable protocol for a type to be comparable using == and !=.

Generics

Swift generics look much the same as generics in C++, Java and C# in that we can declare generics on a function like this

func add<T>(item: T) {
}

We can also add constraints to the genric type like this

func compare<T: Comparable>(a: T, b: T) -> Bool {
}