Skip to main content

How would you limit a function or a class to a specific iOS version?

¡ 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 by using availability attributes. This language feature allows us to add support for newer APIs, methods, or classes while still maintaining backwards compatibility.

if #available(iOS 15 , *) {
print("Hi! I can only run on iOS 15 and up.")
} else {
print("I'll handle all other iOS versions.")
}

// The compiler will present an error if you try and use this
// class on any version < iOS 14.0
//
// The * serves as a wildcard and will allow this class to be available
// on all other platforms.
//
// We can just as easily check for macOS, watchOS, etc.
@available(iOS 14 , macOS 10.10, *)
final class HelloWorld {

}

// We can also mark a function as unavailable in a similar manner
@available(*, unavailable)

Is an enum a value type or a reference type?

¡ 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, an enum is a value type (other value types include structs and tuples).

In the following example, you’ll see that changes made to one variable have no effect on the other.

This is because the value is simply copied from one variable to another - they don’t point to the same location in memory.

enum CarBrands {
case porsche
case mercedes
}

var porsche = CarBrands.porsche
var mercedes = porsche

porsche = .mercedes

print(porsche) // Output: mercedes
print(mercedes) // Output: porsche

Is Swift a statically-typed language?

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

Main Source: 🔗 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

Yes, Swift is a statically-type language - a language where variable types are known at compile time.

In order for a Swift program to compile successfully, the compiler must have all of the necessary information about the types of every class, function, and property. To make things easier for the programmer and to help reduce the verbosity of our code, Swift supports type inference. This enables a compiler to deduce the type of a particular expression automatically when it compiles your code, simply by examining the values you provide.

That's why this code is totally valid even though we're not explicitly specifying the type:

var isApproved = true

In contrast to a statically typed language, we have dynamically typed languages. In a language of this variety, the types aren't determined at compile time, but are deferred until the actual point of sending a message to that object. In other words, the type is only known at runtime.

In Python, a dynamically typed language, the following code will compile, but will throw a runtime error due to the mismatched types:

def willThrowRuntimeError():
s = 'acetheiosinterview' + 1

On what thread should all UI updates be made?

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

Main Source: 🔗 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

All UI updates should be performed on the Main Thread.

When your application launches, the UIApplication is set up on the Main Thread. From here, all of the views in your app ultimately descend from this UIApplication instance.

This means that any interaction with a UIButton or changes to the text in a UILabel have to be performed on the Main Thread as the application itself is configured to run on the Main Thread.

Furthermore, pending changes to the UI are not applied immediately. They are drawn at the end of the thread’s run loop. So, to ensure all of the UI updates are applied together (and are applied without any flickering or visual side effects), it’s important to have them all queued on the same thread.

If we had multiple threads of varying priorities and computational abilities performing UI updates, then the UI would flicker or be unpredictable as each thread would be changing the UI independently of the state reflected in another thread.

Moreover, it’s important to remember that the Main Thread is far more performant than all other threads. If we were to update our UI on the background thread (which has a lower priority and limited resources), the task would first be added to a queue of other potentially long-running tasks (i.e. downloading a file). Now, our UI update will not be applied until all of the other higher priority and previously queued tasks finish executing.

To avoid that poor user experience and to keep things simple, we delegate all of the “real-time” activity to the Main Thread. Since the Main Thread has so much more computational ability, delegating UI-centric tasks to it helps ensure the system applies these UI updates as fast as possible.

What are compilation conditions?

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

Main Source: 🔗 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

Compilation conditions or compiler directives are keywords we can use to influence the compilation process itself. We can use system compiler directives like DEBUG or easily define our own custom flags in our project’s build settings.

Here’s an example of compilation conditions in action:

#if DEBUG
print("...")
#else

#if targetEnvironment(simulator)
return ViewControllerA()
#else
return ViewControllerB()
#endif

#if BETA_TARGET
let image = UIImageView(image: UIImage(named: "BetaAppIcon"))
#else
let image = UIImageView(image: UIImage(named: "NormalAppIcon"))
#endif

Since these compiler directives are considered only at compile time, they allow us to literally exclude the other section of code from the compiled executable. This is useful for ensuring that any new feature you’re not quite ready to release (i.e. feature flagging) or functionality that should be specific to a platform or software version isn’t accidentally included in your final executable.

With this approach, you can instruct the compiler to include only select parts of your codebase in the final executable while still allowing all of the code to exist in the same project.

What are generics and what problem do they solve?

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

Main Source: 🔗 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

Generic classes and functions not only allow you to write more reusable code, but they also help you write less code overall. In simple terms, generics allow you to write the code once, but apply it to a variety of different data types.

While the syntax appears daunting at first, after some practice, generics become much more approachable.

Consider a function,exists(), which simply checksif some specific value exists within an array of elements:

func exists<T: Equatable>(item:T, elements:[T]) -> Bool

This function declaration states that we are going to pass something intoexists()that conforms to Equatable(see <T: Equatable>). We don't know what entity that will be yet, but we promise it will implement the Equatable protocol when the time comes.

We use the variable T to represent this placeholdertype. The method declaration then continues on to say that the remaining parameters will also expect some input matching the type of T.

With this method, we can use an input array of anyEquatabletype and easily check if it contains the target element -item. We are free touse any custom objects, structs, primitive data types, or anything else that implementsEquatable.

Since the items in elements all implement the Equatable protocol, we can simply use == to check for equality:

// Here's our generic exists() function
func exists<T: Equatable>(item:T, elements:[T]) -> Bool {
for element in elements {
if item == element {
return true
}
}

return false
}

// The main takeaway here is that we've written the code once, but

// we can apply it over a wide variety of data types.

// Output: true
exists(item: "1", elements: ["1", "2", "3", "4"]))

// Output: false
exists(item: - 1 , elements: [ 1 , 2 , 3 , 4 ])

// Output: true
exists(item: CGPoint(x: 0 , y: 0 ), elements: [CGPoint(x: 0 , y: 0 ),
CGPoint(x: 0 , y: 1 ),
CGPoint(x: 0 , y: 2 )])

Here's an advanced Swift example:

extension UIView {
// We're saying that `T` is eventually going to be a `UIView`.
//
// It could be a `UIButton`, `UIImageView`, or
// an ordinary `UIView` - it doesn't matter.
//
// Now, we can load any `UIView` or a subclass from a `.nib`.

class func fromNib<T: UIView>() -> T {
return Bundle(for: T.self).loadNibNamed(String(describing:T.self), owner: nil, options: nil)![ 0 ] as! T
}
}

// Usage: let view = RestaurantView.fromNib()
// Usage: let cell = PictureCell.fromNib()
// Usage: let photoGallery = GalleryView.fromNib()

What are layer objects?

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

Main Source: 🔗 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

Every UIView has a CALayer property which is responsible for the actual rendering of visual content and animations.

That’s why we’ll often write code like this:

layer.shadowOpacity = 0.3
layer.shadowRadius = 2
layer.shadowOffset = CGSize(width: 0 , height: 2 )
layer.borderWidth = 0

We’re giving instructions directly to the rendering component of the UIView.

There is an important distinction between the UIView and the CALayer. The UIView takes care of its own layout and placement, but it is the CALayer that is responsible for rendering its actual contents, including borders, shadows, corner radius, complex animations, and other visual effects.

What are our options for storage and persistence?

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

Main Source: 🔗 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

Some of the options include:

User Defaults

We can useUserDefaultsto store simple key-value pairs in an insecure manner. Typically, you would only use UserDefaults to store something lightweightlike a user setting.

.plist

We can use a .plist to store larger data sets. It’s a really flexible human-readable format.

Keychain

This is the only encrypted persistent storage option available on iOS and is used for storing highly sensitive key-value pairs (primarily credentials).

Disk Storage

We can serialize data, domain models, or other downloaded content and save them directly to disk.

Core Data / SQLite

Useful in cases where we have larger data sets and are interested in making queries on the data.

What are our options for unwrapping optionals in Swift?

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

Main Source: 🔗 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

What are our options for unwrapping optionals in Swift?​

We have seven options for unwrappingOptionalsinSwift with varying levels of safety:

var username: String?
var user: User?

// Forced unwrapping (unsafe)
let forcedUnwrapping: String = username!

// Implicitly unwrapped (often unsafe)
// Used when a variable will start off as nil, but will have
// a value by the time you use it.
@IBOutlet var titleLabel: UILabel!

// Optional chaining (safe)
print(user?.emailAddress)

// Optional binding (safe)
if let value = username {

}

// Nil coalescing operator (safe)
let value = username ?? "unknown"

// Guard (safe)
guard let username = username else {
return
}

// Optional (safe)
if case let value? = username {
print(value)
}

What are some of the main advantages of Swift over Objective-C?

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

Main Source: 🔗 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

Swift is easier to read Objective-C is constrained by C conventions, and arguably cannot be updated unless C itself is updated. By doing away with many legacy conventions like semicolons and parentheses for conditional expressions, Swift is simpler, easier to read, and more expressive.

Swift is safer Although a message sent to a nil object is perfectly acceptable in Objective-C and is treated as a no-op, Swift's explicitness about dealing with potentially nil values (Optionals) allows programmers to write cleaner, more transparent, and safer code. Finally, Swift’s strongly typed nature is another point in its favor.

Swift is less verbose I don't think I have to explain this one. If you've used Objective-C, you'll know what I mean (no need for header files, automatically generated memberwise initializers for structs, syntax is simpler, etc).

Swift is faster Swift's performance has been greatly improved by dropping legacy C conventions. With Apple’s continued support, this will likely only improve over time.

Swift has fewer namespace collisions Since Objective-C does not have formal support for namespaces, it‘s common to use a prefix for all class names to avoid conflict with external dependencies and modules. However, Swift provides implicit namespacing which sidesteps this problem without the need for prefixing class names.

Swift has greater ARC support With Swift, Automatic Reference Counting (ARC) is supported across all procedural and object-oriented code paths. Objective-C, on the other hand, only supports ARC within its object-oriented code and Cocoa APIs; it is not yet available for other APIs, like Core Graphics, and procedural C code.

Swift goes beyond iOS

Swift is truly cross-platform - not only can you use Swift to develop applications for iOS, iPadOS, tvOS, and macOS, but you can also use Swift for server-side development too (see Vapor).