Skip to main content

44 questions tagged with "Swift"

Swift tag description

View All Tags

What does the associatedtype keyword do?

· 2 min read
Ace the iOS Interview
Aryaman Sharda
Sources & Resources

Main Source: 🔗 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

Imagine we have the following protocol:

protocol Stack {
func push(x: Int)
func pop() -> Int?
}

This protocol would allow whatever entity conforms to it to have the functionality of a Stack.

But what if we wanted our Stack to work with Doubles or Strings? Our only option would be to duplicate the protocol and change the types:

protocol IntStack {
func push(x: Int)
func pop() -> Int?
}

protocol DoubleStack {
func push(x: Double)
func pop() -> Double?
}

Clearly, this approach would be a little silly and is obviously not scalable. Luckily, this is exactly the problem that associated types can help us solve.

The associatedtype keyword allows us to provide a placeholder for the type of the entity that will eventually implement this protocol.

This allows us to generalize our Stack:

protocol Stack {
associatedtype Element
func push(x: Element)
func pop() -> Element?
}

In the example above, we’ve declared this new type Element which doesn’t exist anywhere else in our code. Now, whenever we implement this protocol,Element will be replaced with the type of the entity implementing Stack.

class IntStack: Stack {
func push(x: Int) {
}

func pop() -> Int? {
}
}

class StringStack: Stack {
func push(x: Int) {
}

func pop() -> Int? {
}
}

The compiler is able to infer that Element should be an Int and a String respectively based on our implementations of push() and pop() and the type of the parameters we’ve specified.

You can always explicitly specify what the substituted type should be if you prefer:

class IntStack: Stack {
typealias Element = Int
func push(x: Int) {
}

func pop() -> Int? {
}
}

Now, our protocol is extremely extensible withoutus having to duplicate any code.

What does the CaseIterable protocol do?

· One min read
Ace the iOS Interview
Aryaman Sharda
Sources & Resources

Main Source: 🔗 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

As the name suggests,CaseIterable is a protocol that provides a handy way of iterating through all of the individual cases in an enum.

When using an enum that conforms to CaseIterable,you can access a collection of all of the enum’s cases by using the allCases property:

enum CompassDirection: CaseIterable {
case north, south, east, west
}

// "There are 4 directions."
print("There are \(CompassDirection.allCases.count) directions.")

let caseList = CompassDirection.allCases.map({ "\($ 0 )" })
.joined(separator: ", ")
//"north, south, east, west"
print(caseList)

allCases provides the cases in the order of their declaration.

If your enum does not contain any associated values or availability attributes, Swift will automatically synthesize conformance to the CaseIterable protocol for you.

What does the final keyword do?

· One min read
Ace the iOS Interview
Aryaman Sharda
Sources & Resources

Main Source: 🔗 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

Imagine you are working on a SDK or a particularly sensitive piece of code - something that needs to be performant, secure, etc. You’d likely want to ensure that your code is used in exactly the way you intend it to be.

That’s where the final keyword comes in. The final keyword prevents aclassfrom being subclassed / inherited from and indicates to other developers that thisclassisn’t designed to be subclassed.

So, in the SDK example, you would likely mark relevant classes asfinalto ensure your code is used only in the way you intended.

Marking properties and functions as final tells theSwift compiler that the method should be called directly (static dispatch) rather than looking up a function from a method table (dynamic dispatch).

This reduces function call overhead and provides a small boost in performance.

What does the lazy keyword do?

· 2 min read
Szymon Michalak
iOS Developer
Sources & Resources

Main Source: 🔗 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

The lazy keyword allows you to defer the initialization of a variable until the first time it’s used; it’s similar to the concept of "lazy loading".

In the following example, the fake User variable will not be initialized until the first time the property is accessed. This allows us to prevent the slow process of creating the object until we’re absolutely sure we’ll need it:

lazy var fakeUser = try! User(dictionary:
JSONService.parse(filename:"FakeUserJSON"))

There’s a few important things to note when working with lazy variables.

Firstly, a lazy property must always be declared asa variable. The lazy property won’t have an initial value until after the containing object’s initialization is complete. So, we’ll need to be able to update the variable’s value at a later point in the application’s execution. Moreover, the initial value of the lazy property could be dependenton some outside factors which means the appropriate initialization value cannot be determined until runtime. So, for both these reasons, the lazy property needs to be mutable.

It’s also common to create a lazy property for objects that are computationally expensive to initialize as there’s no point creating an expensive resource if it’s never used.

// Creating a DateFormatter is expensive.
// lazy lets us ensure we only create it if we need it.
private lazy var dateFormatter:DateFormatter = {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm"
return dateFormatter
}()

It’s also important to understand the distinction between a lazy property and a computed property. A computed property regenerates its value every time it’s accessed whereas a lazy property creates its value once and then maintains it for the rest of the app’s execution.

What does the mutating keyword do?

· 2 min read
Ace the iOS Interview
Aryaman Sharda
Sources & Resources

Main Source: 🔗 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

In Swift,structsare value types which means the properties contained within are immutable by default. So, if we want to be able to modify the values within astruct, we’ll need to use the mutating keyword. This keyword only applies to valuetypes as reference types are not immutable in this way.

Whenever we call a function that uses this keyword and modifies the struct's properties, Swift will generate a new struct in-place with the modifications applied and will overwrite our original struct.

struct User {
var firstName = "Aryaman"
var lastName = "Sharda"

func makeLowercase() {
// The following lines cause a compilation error:
// Cannot assign to property: 'self' is immutable
firstName = firstName.lowercased()
lastName = lastName.lowercased()
}
}

Let’s add the mutating keyword:

struct User {
var firstName = "Aryaman"
var lastName = "Sharda"

mutating func makeLowercase() {
firstName = firstName.lowercased()
lastName = lastName.lowercased()
}
}

When working withmutatingfunctions, we’ll need to declare the as a variable since we’re making changes to the struct's properties:

let user = User()

// Compilation Error!
// Cannot use mutating member on immutable value: 'user' is
// a 'let' constant
user.makeLowercase()

When we make it a variable, we have no such issue:

// No error
var user = User()
user.makeLowercase()

What does the nil coalescing operator do?

· One min read
Ace the iOS Interview
Aryaman Sharda
Sources & Resources

Main Source: 🔗 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

We can use the nil coalescing operator ?? to providea default value in an expression involving an Optional. If the Optional resolves to nil, our default value will be used instead.

var username: String?

// Output: Hello, stranger!
print("Hello, \(username ?? "stranger")!")

username = "@aryamansharda"

// Output: Hello, @aryamansharda!
print("Hello, \(username ?? "stranger")!")

What does the rethrows keyword do?

· 2 min read
Ace the iOS Interview
Aryaman Sharda
Sources & Resources

Main Source: 🔗 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

You’re likely already familiar with the throws keyword which is one of the simplest mechanisms for propagating an error in our code.

Swift also includes the rethrows keyword which indicates that a function accepts a throwing function as a parameter. More specifically, functions declared with the rethrows keyword must have at least one throwing function parameter.

Consider Swift’s map function:

public func map<T>(_ transform: (Element) throws -> T) rethrows -> [T]

Simply speaking, we know that map takes in some function as input and applies that to every element in an array. If the passed in function doesn’tthrow, then we can call map without try:

func doubleInput(_ input: Int) -> Int {
input * 2
}

[ 1 , 2 , 3 , 4 , 5 ].map { doubleInput($ 0 ) }

This is all pretty normal so far, but what if the function passed into map can throw an error? In that case, we’ll have to call map with try:

func doubleInput(_ input: Int) throws -> Int {
guard input != 0 else {
throw Error.invalidRequirement
}

return input * 2
}

try [ 1 , 2 , 3 , 4 , 5 ].map { doubleInput($ 0 ) }

The takeaway here is that if map was instead declared with throws, in both examples we’d have to call map with try even if the passed in function didn’t throw. This would be inelegant and would clutter our code with unnecessary try statements.On the other hand, if map were declared without throws or rethrows we wouldn’t be able to pass in a throwing function to begin with.

The rethrows keyword allows us to handle both cases elegantly - throwing and non-throwing functions. It enables us to create functions that don’t necessarily throw errors of their own, but simply forward errors from one or more of their function parameters when applicable.

What does the typealias keyword do?

· 2 min read
Ace the iOS Interview
Aryaman Sharda
Sources & Resources

Main Source: 🔗 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

The typealias keyword allows us to define our own keyword to represent an existing data type in our application. This does not create a new type - it simply provides a new name for an existing type.

An excellent example is Swift's Codable protocol which is implemented as a typealias.

public typealias Codable = Decodable & Encodable

By the way, did you notice that you can combine protocols with & in Swift?

Now, whenever the compiler sees Codable,it will replace it with Decodable & Encodable and continue compilation.

Imagine we were trying to represent time on a clock. We could use typealias to make the tuple representing our clock easier to work with:

typealias ClockTime = (hours: Int, min: Int)

func drawHands(clockTime: ClockTime) {
print(clockTime.hours) // 3
print(clockTime.min) // 30
}

ClockTime( 3 , 30 )

If you wish to make the typealias accessible throughout your codebase, declare it outside of any class or enclosing type. Otherwise, the typealias will be limited in scope as any other variable would be.

// Accessible across the codebase
typealias ClockTime = (hours: Int, min: Int)

class HelloWorld {
// Only available within this class
typealias Greeting = String

func sayGreeting(greeting: Greeting) {}
}

It's easy to overuse typealias and thereby limit the discoverability of your code, so it’s important to exercise a little restraint.

What is a raw value in an enum?

· 3 min read
Ace the iOS Interview
Aryaman Sharda
Sources & Resources

Main Source: 🔗 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

Enumerations provide a common type for a group of related values thereby enabling you to work with those values type-safely within your code.

While we can use associated values to relate some required data to an enum case, we can use an enum ’s rawValue property in instances where a hard-coded default value will suffice.

Here, ASCIIControlCharacter specifies that the type of its rawValue is going to be a Character.

This, in turn, requires us to provide a hard-coded Character for every case in our enum. So, we can easily link an ASCII character to its respective enum case.

enum ASCIIControlCharacter: Character {
case tab = "\t"
case lineFeed = "\n"
case carriageReturn = "\r"
}

By default, the specified rawValue type can be a String, Character, Integer, or Float, but you can add support for a custom type by adding conformance to RawRepresentable.

The hard-coded value you assign must be unique within the enum’s declaration.

If our enumeration’s rawValue is an Integer or a String,Swift will automatically assign default values for us. However, we’re still able to override the default values if need be.

When it comes toIntegers, the implicit value foreach case is one more than the last. If no value is set for the first case, therawValuewillstart counting up from 0.

Consider thisenumthat specifies the planets and their respective position from the Sun:

enum Planet: Int {
case mercury = 1 , venus, earth, mars, jupiter, saturn, uranus, neptune
}

Since we’ve specified an explicit value of 1 for mercury(instead of the Swift default of 0), Planet.mercury’s rawValue will be 2, Planet.earth will be 3, and so on.

In the case of a String, Swift will set the default rawValue to match the enum case’s name. In the following example, we don’t need to explicitly provide any rawValues as the Swift defaults will work for us.

enum CompassPoint: String {
case north, south, east, west
}

CompassPoint.north.rawValue will be “north”, CompassPoint.south.rawValue will be “south”, and so on.

If we define an enumeration with rawValue support, Swift will automatically add an initializer that allows us to go from the rawValue to the corresponding type.

For example, we could create a reference to Planet.uranus by simply writing:

let possiblePlanet = Planet(rawValue: 7 )
// possiblePlanet is of type Planet? and equals Planet.uranus

Since not all rawValues (ex. Planet(rawValue: 20))can be mapped to a corresponding enum case, using this initializer will return an Optional.

That’s why possiblePlanet is a Planet?.

What is an anonymous function?

· One min read
Ace the iOS Interview
Aryaman Sharda
Sources & Resources

Main Source: 🔗 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

An anonymous function is a function definition that isn’t bound to an identifier. For example, most closures are considered anonymous functions. They can help make code more readable by allowing you to define all of the relevant logic in one place.

You will have almost certainly used anonymous functions in your own Swift code. Notice how in the following examples, functions like { self.view.backgroundColor = .orange } and { $ 0 * 2 } are defined without explicit function namesattached to them.

func performAnimation() {
self.view.backgroundColor = .orange
}

// Without anonymous function
UIView.animate(withDuration: 1.0, animations: performAnimation)

// With anonymous function
UIView.animate(withDuration: 1.0) {
self.view.backgroundColor = .orange
}

// Without anonymous function
func doubleNumbers(num: Int) -> Int {
return num * 2
}

let input = [ 1 , 2 , 3 , 4 , 5 ]

// Without anonymous function
let result = input.map(doubleNumbers(num:))

// With anonymous function
let resultAnonymous = input.map { $ 0 * 2 }