Skip to main content

What is an inout parameter?

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

Main Source: 馃敆 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

Whenever you pass a value type into a function, only a copy of the value is passed along. As a result, even if you attempt to change the value of that parameter inside the function, the variable at the calling site will still maintain its original value.

var currentAge = 26

func updateAge(passedInAge: Int){
var passedInAge = passedInAge
passedInAge = 42
print(passedInAge) // 42
print(currentAge) // 26
}

updateAge(passedInAge: currentAge)

If we want to change the value of the parameter itself instead of just working with a copy of the data, we鈥檒l need to add the inout keyword. This will allow us to make changes directly to the variable that was passed in even if it鈥檚 a value type.

We鈥檒l need to use the & symbol when providing an inout parameter:

var currentAge = 26

func updateAgeWithInout(passedInAge: inout Int) {
passedInAge = 42
print(passedInAge) // 42
print(currentAge) // 42
}

// currentAge is 26 before the call and 42 after
updateAgeWithInout(passedInAge: &currentAge)

The inout keyword is used very often in Swift and enables syntactic sugar like the += operator which modifies the value of the variable on the left-hand side of the operator in place.

What is automatic reference counting?

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

Main Source: 馃敆 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

In simple terms, ARC is a compile time feature that helps us manage memory on iOS. ARC simply counts how many strong references there areto an object and when the count is zero, that object can be freed from memory.

Remember only a strong reference increases the retaincount; a weak or unowned reference has no effect on the object鈥檚 retain count. In iOS, a strong reference is the default.

Imagine that there is some UIViewController that implements a delegate. Since we鈥檙e using a strong reference, we know the UIViewController is intentionally increasing the delegate鈥檚 retain count to prevent it from being cleared from memory. In turn, the delegate has a weak reference back to the UIViewController so there鈥檚 no change to the UIViewController鈥檚 retain count.

Instead, if we had a strong reference from the delegate back to the UIViewController, that would increment the retain count of the UIViewController.

Now, both items would have a retain count of one and neither object could ever be freed; the UIViewController depends on the delegate and the delegate depends on the UIViewController.

This is what we call a retain cycle.

We can prevent this retain cycle by making the delegate have a weak reference to the implementing object. Oftentimes, whenever a child object has a reference to its parent object, we鈥檒l make it a weak reference in order to avoid this exact issue.

That鈥檚 why you鈥檒l commonly see delegates declared with the weak keyword like this:

class LocationManager {
weak var delegate: LocationManagerDelegate?
}

Note that the delegate is an Optional as anything with a weak reference can be nil during its execution.

What is Behavior Driven Development?

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

Main Source: 馃敆 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

Behavior Driven Development (BDD) is a software development methodology that aims to document and design an application based around the behavior the end user is expected to experience. In other words, it is behavior and results focused instead of implementation focused.

For example, when you鈥檙e writing traditional unit tests, you鈥檒l often write tests for all of the various inputs a particular function could expect to receive and assert that the result matches what you鈥檇 expect.

This approach though is entirely focused on the implementation details of your application. As a result, you spend more time testing particulars of the implementation over the actual business logic of your application. BDD strives to tip the balance in the other direction by focusing on testing what your application does insteadof how it does it.

In BDD, when writing tests, you鈥檒l start with a user story and model your tests around the expected behavior for the end user. While Swift doesn鈥檛 support writing BDD style tests natively, popular frameworks like Quick & Nimble allow us to add BDD style testing to the iOS ecosystem.

Here鈥檚 an example test case written in the BDD style - notice that we鈥檙e focusing on testing the end result instead of how we get there:

describe("the favorite button") {
it("is selected if the participant is already a favorite") {
favorite = TestUtils.fakeFavorite()
createFavoritesViewController()
expect(viewController.favoriteButton.isFavorited).to(beTrue())
}

it("is not selected if the participant is not already a favorite") {
favorite = nil
createFavoritesViewController()
expect(viewController.favoriteButton.isFavorited).to(beFalse())
}
}

describe("Email/Password Authentication") {
context("when user presses sign in") {
it("shows login view") {
homeScreenViewController.signInButton
.sendActions(for: .touchUpInside)
expect(navigationControler.topViewController)
.toEventually(beAKindOf(LoginViewController.self))
}
}

context("when user presses sign up") {
it("shows sign up and interactor not called") {
homeScreenViewController.registerButton
.sendActions(for: .touchUpInside)
expect(navigationControler.topViewController)
.toEventually(beAKindOf(RegisterViewController.self))
}
}
}

BDD tests follow the Given, When, and Then format.

If we consider the examples above, we start with the describekeyword which clarifies the action or behavior we鈥檙e looking to test and establishes any preconditions for the test given.

Next, the context block describes the conditions inwhich this behavior should be expected when.

Finally, the it block specifies the expected behaviorand validates the results then.

Typically, you鈥檇 start with the user story and work backwards to fill out these sections.

This style of writing tests very closely models written language and as a result BDD style tests are often more readable and easily maintained than their unit testing counterparts. This style of testing also makes the expected behavior extremely clear and is reasonably self-documenting.

What is code coverage?

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

Main Source: 馃敆 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

Code coverage allows you to measure what percentage of your codebase is being exercised by your unit tests. More importantly, it helps you identify what sections of your codebase your tests don鈥檛 cover.

This option is disabled by default.

To enable it for your project, edit the target鈥檚 settings and select the Code Coverage checkbox:

Now, when you run your tests, you鈥檒l see a breakdown of what areas of your codebase are covered by tests.

What is Dynamic Type in iOS?

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

Main Source: 馃敆 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

Introduced in iOS 10, Dynamic Type allows developers to automatically scale their application's font size up or down to accommodate users with accessibility issues or users that need increased visibility. It can also accommodate those who can read smaller text allowing more information to appear on the screen.

Developers can choose to support Dynamic Text on a view-by-view basis. If you choose to add support for Dynamic Text, you can usetraitCollection.preferredContentSizeCategory to retrieve the user's preferred content size and modify your UI styling accordingly.

You could also implementtraitCollectionDidChange()which will notify you when the user鈥檚 preferred content size setting changes. Then, you can make whatever additional UI changes you need to make based off of the new value in traitCollection.preferredContentSizeCategory:

override func traitCollectionDidChange(
_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
// Use this property to update your application's text styling
traitCollection.preferredContentSizeCategory
}

If you override this function, make sure you always call super.traitCollectionDidChange(previousTraitCollection)first.

What is operator overloading?

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

Main Source: 馃敆 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

Operator overloading allows you to change how existing operators interact with custom types in your codebase. Leveraging this language feature correctly can greatly improve the readability of your code.

For example, let鈥檚 say we had a struct to representmoney:

struct Money {
let value: Int
let currencyCode: String
}

Now, imagine we鈥檙e building an e-commerce application. We鈥檇 likely need a convenient way of adding up the prices of all items in our shopping cart.

With operator overloading, instead of only being able to add numeric values together, we could extend the+operator to support adding Money objects together. As part of this implementation, we could even add logic to convert one currency type into another!

When we want to create our own operator, we鈥檒l need to specify whether it's of theprefix, postfix, orinfixvariety.

prefix: describes an operator that comes before thevalue it is meant to be used with (e.x. !isEmpty)

postfix: describes an operator that comes after thevalue it is meant to be used with (e.x. the force-unwrapping operator -user.firstName!)

infix: describes an operator that comes in betweenthe value it is meant to be used with and is the most common type (e.x. +, -,* are all infix operators)

In order to take advantage of this language feature, we just need to provide a custom implementation for the operator in our type's implementation:

struct Money {
let value: Int
let currencyCode: String

static func + (left: Money, right: Money) -> Money {
return Money(value: left.value + right.value, currencyCode: left.currencyCode)
}
}

let shoppingCartItems = [
Money(value: 20 , currencyCode: "USD"),
Money(value: 10 , currencyCode: "USD"),
Money(value: 30 , currencyCode: "USD"),
Money(value: 50 , currencyCode: "USD"),
]

// Output: Money(value: 110, currencyCode: "USD")
print(shoppingCartItems.reduce(Money(value: 0 , currencyCode: "USD"), +))

What is optional chaining?

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

Main Source: 馃敆 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

Optional chaining is a convenient way of unwrapping multiple Optional properties sequentially. If any of the Optional values in theexpression resolve tonil, the entire expression will resolve tonil.

Consider the following expression involving multipleOptionalproperties:

user?.isAdmin?.isActive

If user or isAdmin or isActive is nil, the entire expression becomes nil. Otherwise, isActive will return the unwrapped value.

This is much more readable than using multiple guard and if let statements to break down a sequence of Optional values.

What is protocol composition? How does it relate to Codable?

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

Main Source: 馃敆 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

The Codable protocol is a prime example of Swift鈥檚protocol composition feature which allows you to easily combine existing protocols together using the & operator.

For example, the Codable protocol is actually thecombination of the Encodable and Decodable protocols.

typealias Codable = Decodable & Encodable

Decodable allows a type to decode itself from an external representation and Encodable allows a type to encode itself as an external representation.

When combined together like this, the Codable protocol ensures that whatever object implements this protocol can both convert and be converted from some externalrepresentation.

In practice, this typically means converting a JSON response to a domain model object and vice-versa.

What is Snapshot Testing?

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

Main Source: 馃敆 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

Snapshot Testing involves comparing the UI of an application against a set of reference images to ensure correctness.

If the difference between the actual UI and the reference image exceeds some custom threshold, the test fails. This is a really convenient way to ensure that there are no unexpected changes or regressions made to the UI of your application.

While UI tests allow us to test the functionality of our application, snapshot tests focus more on verifying that the implementation matches the agreed upon designs. Snapshot Testing is often easier to implement and update than writing traditional UI tests. Plus, it lets you easily verify the application鈥檚 appearance across a variety of device sizes. Lastly, if you change the UI of your application, you鈥檒l be forced to update its corresponding snapshot test which helps ensure your testing suite remains up to date.

Currently, Snapshot Testing is not natively supported through Xcode. However, there are several open-source iOS libraries that enable you to add Snapshot Testing support to your app.

What is Test Driven Development?

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

Main Source: 馃敆 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

Test-Driven Development (TDD) is a software development process relying on software requirements first being converted to test cases prior to writing any production code. Then, the correctness of all subsequent development is measured against those tests.

A typical TDD workflow would look like this:

  1. Write a single test for the functionality you intend to build or amend.
  2. Run the test and ensure that it fails (which it should as the functionality is not yet built).
  3. Write just enough code to ensure the test passes.
  4. Refactor the code and perform whatever clean up is necessary (remove duplication, Single Responsibility Principle, etc.)
  5. Rinse and repeat all the while building up new features and tests simultaneously.

This approach is useful in helping reduce the frequency of regressions in your application and in increasing your project鈥檚 code coverage.

Writing these tests early on helps provide documentation about how the app is expected to behave. TDD promotes writing modular and testable code as it forces the developer to think in small units of functionality that can be easily tested.

In discussions of TDD, you may often see it broken down into 3 stages - Red, Green, and Refactor.

Red: Create a unit test that fails.

Green: Write just enough production code to make your test pass.

Refactor: Once your tests are passing, you鈥檙e free to make any changes to your code. This is your opportunity to clean up your implementation and refine your approach.

TDD only focuses on unit tests and doesn鈥檛 cover UI behavior or integration tests, so it鈥檚 often paired with additional testing paradigms.