Skip to main content

What are the different URLSessionConfiguration options?

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

Main Source: 🔗 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

URLSession is the main object responsible for managing HTTP & HTTPS requests in iOS.

In order to create a URLSession, we need to initialize it with a URLSessionConfiguration type:

URLSession(configuration: .default)

The default URLSessionConfiguration uses a persistent disk-based cache and stores credentials in the user’s keychain.

Next, we have the ephemeral URLSessionConfiguration type.

URLSession(configuration: .ephemeral)

It’s similar to the default configuration, however it doesn’t maintain a cache, store credentials, or save any session-related data to disk. Hence, the name “ephemeral”.

Instead, all session-related data is stored in RAM. The only time an ephemeral session writes data to disk is when you explicitly instruct it to write the contents of a URL to a file.

Finally, we have the background configuration type:

URLSession(configuration: .background(withIdentifier: "IDENTIFIER_NAME"))

This creates a URLSessionConfiguration object that allows HTTP and HTTPS uploads or downloads to be performed in the background. This configuration is most commonly used when transferring data files while the app runs in the background.

A URLSession configured with this type hands controlof the transfer over to the system which then handles the file transfer in a separate process. In iOS, this configuration makes it possible for transfers to continue even when the app is suspended or terminated.

What are the higher order functions in Swift?

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

Main Source: 🔗 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

In mathematics and computer science, a higher-order function is a function that takes one or more functions as arguments or returns a function.

Functional programming is a programming paradigm in which we try to leverage these higher-order functions.

Let's say you wanted to create a function to double all of the numbers in an array. You might write something like this:

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

for i in 0 ..<input.count {
input[i] = input[i] * 2
}

// `input` now equals [2, 4, 6, 8, 10]

However, we've immediately run into a problem. What happens if we want to access the original values ininput? We can no longer retrievethose values.

By changinginput, we’ve used imperative programmingwhich is a paradigm in which executed statements change the state of the program. In contrast, functional programming ensures that no changes are made to the existing state of the application and no side-effects are introduced.

You'll be able to find a more rigorous mathematical definition of functional programming elsewhere, but simply speaking the goal is:

  • Avoid mutability wherever possible.
  • Use functions as the building blocks of functionality. In other words, try to compose functions wherever possible.
  • Create pure functions (a pure function is a function that will always produce the same result for the same input regardless of when and where it is being called from).

Simply put, in imperative programming, changing the variable's state and introducing side effects is permissible, but in functional programming it is not.

You'll notice that in all of the following examples, theinputvariable's values are never changed which allows us to satisfy the immutability requirement. Instead, all of these function calls return a completely new value.

.map {}
// map: Applies a function to every element in the input and returns
// a new output array.
let input = [ 1 , 2 , 3 , 4 , 5 , 6 ]

// We'll take every number in the input, double it, and return it in a new
// array.
//
// Notice that the input array is never modified.
//
// Output: [2, 4, 6, 8, 10, 12]
// $0 refers to the current element in the input array `map` is operating on.
let mapOutput = input.map { $ 0 * 2 }

.compactMap {}
// compactMap: Works the same way as map does, but it removes nil values.
let input = ["1", "2", "3", "4.04", "aryamansharda"]

// We'll try and convert each String in `input` into a Double.
//
// Notice that the input array is never modified and the values that resolve
// to nil are not included in the output.
//
// compactMap is often used to convert one type to another.
// Output: [1.0, 2.0, 3.0, 4.04]
let compactMapOutput = input.compactMap { Double($ 0 ) }

.flatMap {}
// flatMap: Use this method to receive a flattened collection from an
// input that may have several nested structures.
let input = [[ 1 ], [ 2 , 2 ], [ 3 , 3 , 3 ], [ 4 , 4 , 4 , 4 ]]

// This will flatten our array of arrays into just a single array.
// Output: [1, 2, 2, 3, 3, 3, 4, 4, 4, 4]
let flatMapOutput = input.flatMap { $ 0 }

.reduce {}
// reduce: Allows you to produce a single value from the elements in
// a sequence.

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

// This will add up all of the numbers in the sequence.
// `sum` will be 10
let sum = input.reduce( 0 , { x, y in
x + y
})

// You can also write this more simply as:
let sum = input.reduce( 0 , +)
// `sum` will be 10

.filter {}
// filter: Returns an array containing only the elements of the sequence
// that satisfies some constraint(s) while preserving order.
let input = [ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 ]

// This will only return the even numbers.
// Output: [2, 4, 6, 8, 10]
let filterOutput = input.filter { $ 0 % 2 == 0 }

.forEach {}
// forEach: Calls the given closure on each element in the sequence in the
// same order as a for-loop.
let input = [ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 ]

// This will print out the numbers in `input` just like a traditional
for-loop.
input.forEach {
print($ 0 )
}

When you use forEach it is guaranteed to go throughall items in sequence order, but map is free to process items in any order.

.sorted {}
// sorted: Returns the elements of the sequence, sorted using the
// custom sorting logic you specify.
let input = [ 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 ]

// This will create an array with the numbers sorted in descending order.
// Output: [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]

let sortedInput = input.sorted(by: { $ 0 > $ 1 })

These functions can be applied to objects of any type - even custom ones. In addition, combining these calls can deliver impressive functionality with very little code:

// We'll create an array of Cars and specify some basic properties.
struct Car {
let name: String
let horsepower: Int
let price: Int
}

var cars = [Car?]()
cars.append(Car(name: "Porsche 718", horsepower: 300 , price: 60500 ))
cars.append(Car(name: "Porsche 911", horsepower: 379 , price: 101200 ))
cars.append(nil)
cars.append(Car(name: "Porsche Taycan", horsepower: 402 , price: 79900 ))
cars.append(Car(name: "Porsche Panamera", horsepower: 325 , price: 87200 ))
cars.append(nil)
cars.append(nil)
cars.append(Car(name: "Porsche Macan", horsepower: 248 , price: 52100 ))
cars.append(Car(name: "Porsche Cayenne", horsepower: 335 , price: 67500 ))

// Let's return valid cars (not nil) that have a horsepower greater than 300
// and are sorted by descending price.
cars.compactMap { $ 0 }
.filter { $ 0 .horsepower > 300 }
.sorted { $ 0 .price > $ 1 .price }
.forEach { print($ 0 ) }

// Output:
Car(name: "Porsche 911", horsepower: 379 , price: 101200 )
Car(name: "Porsche Panamera", horsepower: 325 , price: 87200 )
Car(name: "Porsche Taycan", horsepower: 402 , price: 79900 )
Car(name: "Porsche Cayenne", horsepower: 335 , price: 67500 )

Functional programming in iOS is becoming more and more mainstream and is critical to the SwiftUI and Combine implementations. Levering these language features and programming paradigms correctly allows you to write optimized, thread-safe, easily testable, and readable code.

What are the purposes of setUp() and tearDown() in the XCTest framework?

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

Main Source: 🔗 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

Whenever we run tests, we'll often want each test to run independently. More specifically, we want to ensure that each test is executed under identical conditions and the end state of one test doesn't inadvertently influence the start state of another test.

TheXCTestframework provides two functions we canuse to ensure these conditions are met - setUp() and tearDown(). These functions allow us toprepare and clean up all allocations and dependencies before the next test is run.

setUp(): This method is called before each test isinvoked and provides us an opportunity to reset state.

tearDown(): This method is called after every testmethod has finished execution and is used to perform any necessary clean up or releasing of resources.

These functions allow us to reuse data more easily in our tests.

For example, we can create instance variables to represent mock data in our XCTestCase class and then utilize the setUp() method to reset the initialstate of these variables for each test and the tearDown() method to perform any necessarycleanup after the test is completed.

What are the stages in a UIViewController’s view lifecycle?

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

Main Source: 🔗 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

Here are the different stages of a UIViewController’s lifecycle:

loadView() This function is responsible for creating the view the UIViewController manages. If you want to create your view manually, you’ll need to override this function.

However, if you’re working with .storyboard or .xib files, you can ignore this method entirely.

loadViewIfNeeded() Loads the UIViewController’s view if it has not yet been loaded.

viewDidLoad() This method is called after the UIViewController has loaded its view hierarchy into memory.

This method is called regardless of whether the view hierarchy was loaded from a .xib file, .storyboard, or created programmatically in the loadView() method. In most cases, you’ll override this method in order to perform additional setup and customization.

viewWillAppear(_ animated: Bool) This method is called before the UIViewController's view is about to be added to a view hierarchy and before any animations are configured for showing the view. You can override this method to perform custom tasks associated with displaying the view. For example, you might use this method to change the orientation or style of the status bar to coordinate with the orientation or style of the view being presented.

According to Apple’s documentation, this method “notifies the UIViewController that its view is about to be added to a view hierarchy”, however this method gets called every time the view is about to appear regardless of whether the view has previously been added to the hierarchy.

viewWillLayoutSubviews() This method is called to notify the UIViewController that its view is about to layout its subviews. In other words, this method is called right before layoutSubviews() is executed.

For example, when a view's bounds change, this function will be called as the view needs to adjust the position of its subviews and the layout will need to be recalculated. Your view controller can override this method to make changes before the view lays out its subviews. The default implementation of this method does nothing.

viewDidLayoutSubviews() As you’d expect, this method is called to notify the view controller that layoutSubviews() has finished execution.

This method being called does not indicate that the individual layouts of the view's subviews have been adjusted as each subview is responsible for adjusting its own layout.

YourUIViewControllercan override this method tomake changes after the view lays out its subviews. The default implementation of this method does nothing.

viewDidAppear(_ animated: Bool) This function notifies the UIViewController that its view has been added to the view hierarchy and is now visible on the screen.

You can override this method to perform additional tasks associated with presenting the view. If you override this method, you must callsuperatsome point in your implementation.

viewWillDisappear(_ animated: Bool) This method is called when the UIViewController is going to be removed from the view hierarchy or will no longer be visible. This method is called before the view is actually removed and before any animations are configured.

viewDidDisappear(_ animated: Bool) This method is called when the UIViewController is removed from the view hierarchy.

What are UI tests?

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

Main Source: 🔗 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

User interface tests allow us to test our application’s behavior from the user’s perspective. It helps us to ensure that our application’s UI interactions, animations, and various user flows continue to work correctly as our application evolves.

While unit testing focuses on the inputs and outputs of the functions within our codebase, UI Testing aims to verify user-facing behavior.

To implement UI tests, we will continue to use theXCTestframework.XCTestwill then leverage the iOS Accessibility System to interact with the various UI components specified by the test.

For example, a simple UI test that ensures the correctness of a login form might look like this:

import XCTest

class LoginViewUITest: XCTestCase {
func testUserLogin() {
// Launch app
let app = XCUIApplication()
app.launch()

// Simulate button press
app.buttons["loginButton"].tap()

// Asserts that the Login form is shown correctly
XCTAssertTrue(app.textFields["usernameTextField"].exists)
XCTAssertTrue(app.textFields["passwordTextField"].exists)
}
}

Xcode can also record and automatically create UI tests based on your interactions with theapplication. For most UI tests, you can just hit Record in Xcode, walk through some flow in your application, and then add the automatically generated test to your test suite.

What common problem does the Main Thread Checker help detect?

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

Main Source: 🔗 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

Quite simply, it’s a means of identifying code that should be running on the main thread but is running on a background thread instead.

Since all UI updates should only be performed on the main thread, the Main Thread Checker is often used to help catch instances of UI updates occurring on a background thread.

To enable it, select Edit Scheme → Test → Diagnostics and toggle on the Main Thread Checker:

With this setting enabled, we’ll get the following warning if we try and update the UI on a background thread:

What do the Comparable, Equatable, and Hashable protocols do?

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

Main Source: 🔗 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

Equatable Implementing this protocol allows one instance of an object to be compared for equality with another instance of an object of the same type. You’ve probably leveraged this protocol without realizing it as theEquatableprotocol is what allowsus to use == to check the equality of two objects.

Adding Equatable support to your object allows your object to gain access to many convenient Swift APIs automatically. Furthermore, since Equatable is the base protocol for Hashable and Comparable, by adding Equatable conformance,we can easily extend our implementation to support creating Sets, sorting elements of a collection, and much more.

Let’s take a look at our Money struct:

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

Currently, there’s no easy way for us to check if one Money object is equal to another Money object.

We can change that by conforming to Equatable:

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

static func == (lhs: Money, rhs: Money) -> Bool {
lhs.currencyCode == rhs.currencyCode && lhs.value == rhs.value
}
}

Now, our adherence to the Equatable protocol and our subsequent custom implementation of the == operator allows us to easily compare any two Money objects for equality.

Technically, we did some extra work though...

Since Int and String types are Equatable themselves, Swift can automatically synthesize the == implementation for us.

So, it’d be sufficient to just write:

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

Hashable When an object implements the Hashable protocol it introduces a hashValue property which is useful in determining the equality of two objects and allows that object to be used with a Set or Dictionary.

The hashValue is an Integer representation of the object that will always be the same for any two instances that compare equally. In simple terms, this means that if we have two instances of an object A and B then if A == B it must follow that A.hashValue == B.hashValue.

However, the reverse isn’t necessarily true. Two instances can have the same hashValue, but may not necessarily equal one another. This is because the process that creates the hashValue can occasionally create situations where two different instances generate the same hashValue.

The Hashable protocol only guarantees that two instancesthat are already known to be equal will also have the same hashValue.

It’s easy to add support for the Hashable protocol, but note that Hashable requires conformance to the Equatable protocol as well.

From Swift 4.1 onwards, we can have the compiler automatically synthesize conformance for us just like we did with Equatable. This functionality is only an option when the properties within the object conform to Hashable.

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

Otherwise, we’ll have to implement the hashValue generation ourselves. Swift 4.2 introduced a Hasher type that provides a randomly seeded universal hash function for us to use in our custom implementations:

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

func hash(into hasher:inout Hasher) {
hasher.combine(value)
hasher.combine(currencyCode)
}
}

Conforming to the Hashable protocol allows you to use this object in a Set or as a Dictionary key. Many types in the standard library conform to Hashable: Strings, Integers, Floats, Booleans, and even Set are hashable by default.

Comparable The Comparable protocol allows us to use our customtype with the <, <=, >=, and > operators.

The implementation of this protocol is quite clever. We only have to implement the less than operator < since the implementations of all ofthe other comparison operators can be inferred from < and our Equatable conformance.

This conformance allows us to use handy Swift methods like sorted(),min(), and max()on our objects in collections.

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

static func < (lhs: Money, rhs: Money) -> Bool {
lhs.value < rhs.value
}
}

What does a deinitializer do?

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

Main Source: 🔗 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

A deinitializer is a function that is called right before aclassis deallocated. Deinitializers are only available onclasstypes and eachclasscan onlyhave one deinitializer. This function does not accept any parameters.

You create a deinitializer using the deinit keyword:

deinit {
// Perform the deinitialization
}

Although Swift automatically deallocates instances when they are no longer needed (the retain count becomes 0), deinitializers allow you to perform additional cleanup before your instance is deallocated.

For example, you may want to invalidate timers, terminate a socket connection, or close out a connection to a database:

deinit {
database.closeConnection()
}

deinit {
timer?.invalidate()
timer = nil
}

Just before releasing an instance, the system will call the deinitializer automatically; do not call it yourself.

What does an unwind segue do?

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

Main Source: 🔗 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

You can use an unwind segue to jump to any UIViewController further up your UIViewController hierarchy while simultaneously destroying all other UIViewControllers in between. More specifically, you can use it to navigate back through one or more push, modal, or popover segues.

Let’s say you’re navigating from ViewControllerA ViewControllerB ViewControllerC and you want to go from ViewControllerC back to ViewControllerA.If you had to rely on just the back button alone, you’d have to go through ViewControllerB first.

With an unwind segue, we can jump directly to ViewControllerA and destroy ViewControllerB along the way.

In ViewControllerA, add:

@IBAction func unwindToViewControllerA(segue: UIStoryboardSegue) { }

You can leave the method body empty.

Then, in the UIViewController you’re departing from ViewControllerC you can drag from the body of the UIViewController to the Exit Icon and select the function we’ve created.

Now, when we press the “Go Directly to A” button, the application will navigate directly from ViewControllerC to ViewControllerA skipping over anddestroyingViewControllerB along the way.

What does app thinning mean?

· 2 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, app thinning - introduced in iOS 9 - helps developers reduce the size of their app’s executable. This reduction in size is accomplished through three mechanisms; slicing, bitcode, and the use of on-demand resources.

Prior to iOS 9, when you installed your app on a device, it would include assets for all device types. For example, you might be on a non-retina screen device, but the app installation would still include retina quality assets. In other words, your application’s executable would include all of the resources required to run on all device types.

Unsurprisingly, this would inflate the size of the installed app with assets the user would never be able to use. So, slicing allows developers to include only the relevant assets for the device on which the app is being installed. This is one of the many conveniences asset catalogs afford us.

Typically, when you upload your app to App Store Connect, you are sending a compiled binary file. When you enable the bitcode setting in Xcode, you’ll send an intermediate representation of the compiled program instead. The compilation will be completed when it’s installed by the user. This delayed approach to compilation allows Apple to identify the device the app is being installed on and pick only the relevant resources and assets to bundle into the final executable alongside introducing additional device-specific optimizations.

On-demand resources are app contents that are hosted on the App Store and are separate from the related app bundle that you download. They enable smaller app bundles, faster downloads, and richer app content. The app requests sets of on-demand resources, and the operating system manages downloading and storage. The app uses the resources and then releases the request. After downloading, the resources may stay on the device through multiple launch cycles, making access even faster.

Nowadays, these settings are enabled by default.