iOS: Beginner’s Guide — UIKit (3/3)

John Kim
6 min readJan 20, 2021

If you haven’t checked out Day 2 in the journey, you can check that out right here.

Polymorphism and Typecasting

The concept of polymorphism is that any instance of a subclass is inherited from its superclass can be treated just as either a subclass or its superclass — it’s essentially the both at the same time.

In short, an object can represent both as its class and its superclass in the same time.

An example of polymorphism

Typecasting

Typecasting is when you are converting an object from one type to another.

There are 3 types, but you will more commonly encounter these two:

  1. as? (optional downcasting) — The conversion might be or might not be true
  2. as! (forced downcasting) — I am confident that this conversion is true and I am aware that the app will crash if I am wrong

To be specific, typecasting doesn’t actually transform anything, and it just converts how Swift treats the object (more like an interpretation). You are informing Swift that an object it believed was type A was actually type B.

An example of optional downcasting

Optional downcasting is most often used with if let statements to safely unwrap the optional at the same time while downcasting or changing the instance or object’s “interpretation”.

An example of forced downcasting

Alternatively, you can do the following as well, which would be more efficient:

An example of forced downcasting

You can also apply optional downcasting at the array level, but it’s a bit tricky because you need to utilize the nil coalescing operator to guarantee that there is a value for the loop.

An example of optional downcasting

You cannot force to convert something to a completely different, unrelated type. Typecasting is only useful when you know something that Swift does not.

Closures

Closures can be interpreted as variables, or more specifically lines that contain code.

NOTE: Closures take a copy of the values that are used inside them (capture the environment where they are created).

UIView is an iOS data type within UIKit that is the most basic type of UI container, and UIView has a method called animate() that manipulates the interface utilizing animations by describing what exactly is changing and over how many seconds the animation is taking place.

An example of UIView’s animate method

Within the closure, what happens is that UIKit takes a copy of the code within the braces, stores it, makes the necessary preparations, and then runs the code when it is ready. Closures capture their environment as the uiView instance is declared outside of the closure, we have access to that instance within the closure.

NOTE: If object A stores a closure as a property and that same property also references object A, then this is called a strong reference cycle.

For trailing closures, the rule is that if the last parameter to a method contains a closure, then you can delete that parameter and replace it with a block of code within braces outside of the parentheses.

An example of a trailing closure

Protocols

Protocols are Swift contracts where you define a set of properties and methods that a type must implement if it conforms to a protocol.

An example of a protocol

There are 3 important things to note with the example above:

  1. Both properties of age and name have { get set } attached to them, which means that the conforming types must also make these properties both gettable (readable) and settable (writeable). Also, the conforming type must assign these properties as variables, not constants.
  2. The workingHard() function has no block of code, which indicates that protocols do not provide how these behaviors are being executed, but it is defining how something should behave.
  3. We cannot create instances of a protocol as it isn’t a type, but we can have types (classes or structs) conform to it.
An example of a protocol used in action

NOTE: We can create an ExampleProtocol array and use instances or objects within that array without knowing what their actual type is (as long as the instances or objects conform to the protocol defined by the array).

Extensions

Extensions allow us to modify Swift’s data types to add new functionality.

An example of an extension

NOTE: The extension multiplies 2 to its input number and returns it to the caller but does not modify its original value.

An example of using the mutating keyword

Swift forces you to use the mutating keyword to indicate that it will change its input, and because the function no longer returns a value, in this case, we use *= to multiply 2 to self.

If you declare a method as being mutating, you cannot use it with constants as Swift knows that it will be changing values.

Extensions are similar to subclasses, but the main reason why the former is used over the latter is because of its extensibility. Extensions are applicable to all data types, and they do not conflict when you have more than one.

Conventionally, we name our extension files as Type + Modifier + .swift

Protocol Extensions

Whereas protocols define contracts that conforming types must adopt, protocol extensions allow us to define how things within the protocol must execute, adding the necessary functionality to all types that conform to the protocol within a single place.

An example of an extension

There are other types of Int in Swift such as:

  1. UInt — an unsigned integer that replaced its capability to hold negative numbers in exchange to hold much larger positive numbers
  2. Int8 — an integer that is consisted of 8 binary digits, holding a maximum value of 127
  3. UInt64 — the largest type of integer that holds up to 18,446,744,073,709,551,615 (18 quintillion and 446 quadrillion…)

Therefore, if we just specify the constant Int as conforming to the Int specific data type, then this code above would not affect other varieties of integers like UInt, Int8, and UInt64 so our solution is using protocol extensions that allow us to modify several data types at once.

There is an important distinction to make:

  1. self indicates “my current value”
  2. Self indicates “my current data type”
An example of using Self

We redefined our extension as applying to BinaryInteger rather than Int because BinaryInteger is a protocol that all of Swift’s integer types conforms to, meaning that all of the integer types now have access to the number method above (which makes it easier so we don’t have to extend each one individually).

An example of a protocol extension

We implement a default method so the types that conform to the protocol do not need to implement the required method themselves unless if they want to (in this case, all conforming types automatically get a workingHard() method defined and implemented by default).

--

--

John Kim

iOS Developer | Full Stack Developer | Software Engineer | LinkedIn: john-kim-developer | GitHub: cloudiosx | Portfolio: cloudiosx.com