Skip to main content

What is your preferred way of creating views?

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

Main Source: 🔗 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

There’s no right or wrong answer here as the question is inherently subjective, but it’s a great opportunity for you to demonstrate that you understand the challenges and limitations with each approach (.xib vs .storyboard vs programmatically).

Storyboards Storyboards are a great way to quickly build out new designs and are useful in encapsulating a particular user flow. By keeping all of the views relevant to an experience in your app in the same storyboard, it makes it easier to get a high-level overview of the applications’ functionality and intended user experience. Storyboards can also make navigating to and from other UIViewControllers very easy via segues.

However, storyboards can be difficult to work with on a team as they’re prone to merge conflicts, long loading times, and responsiveness often suffers as the storyboard increases in size.

XIBs While xibs and storyboards share a lot of similarities, they trade the storyboard’s navigation behavior for increased reusability. Since a xib is specific to one view, there’s no provision of establishing segues from one view to the next. You’ll typically use a xib when you have a single custom component that you want to re-use in multiple locations throughout the app.

Both storyboards and xibs make design changes very cumbersome to implement. For example, if you wanted to change the application’s default font, colors, icons, or some other application-wide change needs to be made, you’d have to go into each storyboard and xib and manually update each view.

There are ways to mitigate this, but the “source of truth” gets a bit lost. Since you’ll often apply additional styling programmatically (like shadows and rounded corners), a developer now needs to check multiple locations to get a full picture of the view’s complete implementation and expected appearance. Last but not least, searching for constraints, custom styling, subviews, images, fonts, etc. is much more difficult on storyboards and xibs than on a view defined programmatically.

Programmatically

While this option can be tedious and is often initially slower than the other options, it allows for greater control, improved searchability, and more reuse.

Any application-wide UI change can be made easily, merge conflicts are easier to manage, there’s a single source of truth, and views created programmatically can be more easily tested.

In practice, you’ll likely find that there’s rarely ever one right approach. Often, production projects will have a mix of all three methods depending on how much reuse and development speed influences the decision making.

What methods are required to display data in a UITableView?

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

Main Source: 🔗 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

Every iOS interview I've done has dealt with UITableViews in some capacity. Most often, it will involve hitting some API and showing the response in a UITableView like we did in Assessment #1.

As you prepare for your interviews, you should aim to be able to create a UITableView with custom UITableViewCell swithout referring to any documentation.Ideally, this set up process should become second nature to you.

Since this topic will be a constant in all of your interviews, knowing precisely what functions are necessary and what their respective inputs, outputs, and method signatures are is crucial.

Here are the only required UITableViewDataSource methods:

// Return the number of rows for the table.
override func tableView(_ tableView: UITableView,
numberOfRowsInSection section: Int) -> Int {
return 0
}

// Provide a cell object for each row.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Fetch a cell of the appropriate type.
let cell = tableView.dequeueReusableCell( withIdentifier:"CellIdentifier", for: indexPath)

// Configure the cell's contents.
cell.textLabel!.text = "Cell text"
return cell
}

What would happen if a struct had a reference type in it?

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

Main Source: 🔗 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

In Swift, value types can contain reference types and reference types can contain value types.

In this case, the result is simply the creation of a value type with a property that has reference semantics. In other words, the reference type behaves like it always does.

Any changes made to the reference type will also be reflected in the property within the value type.

In the following example, theUserobject is a referencetype. You’ll see that changes made to the reference type modify the property in thestruct.

class User {
var name: String
var age: Int

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

struct Article {
var author: User
var id: Int
}

let user = User(name: "Aryaman", age: 26 )
let article = Article(author: user, id: 123 )

// Output: Aryaman
print(article.author.name)

user.name = "Aryaman Sharda"

// Output: Aryaman Sharda
print(article.author.name)

What's the difference between Self vs self?

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

Main Source: 🔗 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

In the context of protocols and extensions, Self refers to the type that conforms to the protocol whereas self refers to the value inside that type.

Consider this extension:

extension Int {
// Self here refers to the conforming type (Int)
func square() -> Self {
// self refers to the value of the Int itself i.e. 2
self * self
}
}

// self in the code above would now equal 2
let width = 2

// Output: 4
print(width.square())

Additionally, you can use Self to limit the conformance of a protocol to only specific types:

protocol Squareable where Self: Numeric {
func square() -> Self
}

extension Squareable {
func square() -> Self {
self * self
}
}

Now, our Squareable protocol is only available to types that conform to Numeric.

When would you use a struct versus a class?

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

Main Source: 🔗 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

Typically, you’ll want to use astructif any of thefollowing conditions apply:

  • Use astructwhen encapsulating simple data types
  • When you need thread safety asstructsare passed-by-value
  • You want pass-by-value semantics
  • When the properties defined inside the entity are mostly value types
  • You don’t need inheritance
  • You don’t need mutability
  • When you want automatic memberwise initializers

Apple’s recommendation is to start with astructandtransition to aclassonly if you need inheritance or pass-by-reference semantics. However, if your entity is storing a lot of data then it may make sense to use aclassso you’re only incurringthe memory cost once.

While iterating through an array, how can we get both the value and the index?

¡ 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 accomplish this easily with the help of the enumerated()function.

let title = ["Ace", "The", "iOS", "Interview"]

for (index, value) in title.enumerated() {
print("Index: \(index), Value: \(value)")
}

// Index: 0, Value: Ace
// Index: 1, Value: The
// Index: 2, Value: iOS
// Index: 3, Value: Interview

The enumerated() function returns a sequence of pairs composed of the index and the value of each item in the array. Then, we can use tuple destructuring and aforloop to go through every element in the sequence.

A common mistake is to apply this function on a Dictionary.If you do this, the Dictionary will be treated as an array of tuples and your output will look like this:

let userInfo: [String: Any] = ["age": 25 , "gender": "male"]
for (index, value) in userInfo.enumerated() {
print("Index: \(index), Value: \(value)")
}

// Index: 0, Value: (key: "age", value: 25)
// Index: 1, Value: (key: "gender", value: "male")

Your app is crashing in production. What do you do?

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

Main Source: 🔗 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

Unfortunately, there’s no one size fits all answer here, but the following explanation might provide a starting point.

The main focus of your answer should be on discussing an approach for isolating the bug, understanding how to replicate it consistently, how you would go about resolving the issue, creating a new release, and finally what preventative measures you would introduce as a result.

You can start off your answer by mentioning that you’ll try and identify the iOS version, app version, and device type of the affected users. Then, if you have access to the device logs, you’ll use them to help you reproduce the crash consistently. Knowing the exact steps that precipitated the crash will allow us to write better tests.

You could also mention that as part of the exploration into the issues, you’d use Exception Breakpoints, Debugger output, crash logs, etc. to help isolate the crash.

Or, if the issue is harder to replicate, you can mention that you might use the Debugger to manually set the application into all of its potential states. For example, if there was a crash during the sign in flow, you might manually put your app into the following states for testing purposes: logged in user, blocked user, account recovery mode, etc.

Subsequently, you might mention that once you’ve identified the offending area of code, you’ll look at recent pull requests that introduced changes in that area and work backwards from there.

You want to demonstrate that you’ll be systematic and methodical in your debugging approach.

Eventually, once you’ve identified a fix, you could talk about how you’ll test the fix thoroughly and ensure that it works across different device types and app configurations (e.g. user account types, languages, regions, etc.).

It may also be prudent to discuss that you’d write additional tests to cover this area of code to ensure the issue doesn’t happen again and to protect against future regressions.

With the fix in place, you can notify your team about the new hotfix and request a code review. You could also mention that if this crash is critical, you would request an expedited review from Apple.

Once the fix has been released, you can discuss how you’ll monitor the new release and ensure that the reported crash numbers do in fact reduce and that your fix was successful.

With the crisis averted, your answer can now focus on how you’d prevent issues like this in the future.

You could bring up leveraging App Store Connect’s phased release feature which will slowly release the new version of your application. This will allow you to prevent untested code from becoming immediately available to all users. With a phased release, if you detect stability issues, you can easily pull the release before it affects a larger percentage of your user base.

Your final point could be about starting a discussion with your team to better understand how this issue made it past your tests and the code review process.

The main objective here is to demonstrate a methodical approach that covers identifying the issue, resolving it, and how you would prevent similar issues moving forward.

What are trailing closures?

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

Main Source: 🔗 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

Trailing closures are simply syntactic sugar in Swift that allow us to implement closures without a ton of boilerplate code. When the final parameter in a call to a function is a closure, trailing closures allow you to define the closure’s contents outside of the function call.

func sayHello(name: String, closure: (() -> ())) -> Void {
print("Hello \(name)")
closure()
}

// Typical syntax
sayHello(name: "Aryaman", closure: {
print("Finished saying hello.")
})

// With trailing closure syntax
sayHello(name: "Aryaman") {
print("Finished saying hello.")
}

While this is convenient, convention is to only use trailing closure syntax when your function only has one closure parameter. If your function has multiple closure parameters, you should adopt the normal syntax instead.

func sayHello(name: String, then: (() -> ()), finally: (() -> ())) -> Void { print("Hello \(name)")
then()
finally()
}

// With trailing closure syntax
sayHello(name: "Aryaman") {
print("This is the then closure")
} finally: {
print("This is the final closure")
}

// Preferred approach
sayHello(name: "Aryaman", then: {
print("This is the then closure")
}, finally: {
print("This is the final closure")
})

You can see in the example above, the first call to sayHello() is harder to read than the second call. When multiple closures are required, it’s hard to discern which one does what. I’d recommend you keep this in mind when completing your take-home assignments. Additionally, if development team are using a linter like SwiftLint, it will also enforce the same convention:

  • Use trailing closure syntax when there is only one closure
  • Use the long form syntax when there are multiple closures

What is the difference between an escaping closure and a non-escaping closure?

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

Main Source: 🔗 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

If you’ve ever tried to pass in a closure as a parameter to a function, you might have encountered a warning from Xcode prompting you to add the @escaping keyword to your parameter declaration. Let’s take a closer look at the difference between an escaping and non-escaping closure. In simple terms, if the closure will be called after the function returns, we’ll need to add the @escaping keyword. Since the closure is called at a later date the system will have to store it in memory. So, because the closure is a reference type, this will create a strong reference to all objects referenced in its body. So, @escaping is used to indicate to callers that this function can potentially introduce a retain cycle and that they’ll need to manage this potential risk accordingly.

escapingClosure {
print("I'll execute 3 seconds after the function returns.")
}

nonEscapingClosure {
print("This will be executed immediately.")
}

func escapingClosure(closure: @escaping (() -> ())) {
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
closure()
}
}

func nonEscapingClosure(closure: (() -> ())) {
// Some synchronous code…
// Calls the closure immediately
closure()
// Some synchronous code…
}

In the case of the nonEscapingClosure, we can see that the closure is called immediately and will not live or exist outside the scope of our function. So, the non-escaping closure variety works for us here. This is also the default type for all closures in Swift. As an added benet, we can use the self keyword here without worrying about introducing a retain cycle.