Skip to main content

44 questions tagged with "Swift"

Swift tag description

View All Tags

Can we use Swift’s reserved keywords as variable or constant names?

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

Main Source: 🔗 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

Yes and it’s accomplished through the use of backticks.

If you want to use a reserved keyword, for example as a case in an enum, you can just add backticks around the reserved keyword:

enum MembershipType {
case `default`
case premium
case trial
}

// Free to use MembershipType.default now

Otherwise, without the backticks, we’d have a compilation issue.

Can you explain what the @objc keyword does?

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

The @objc keyword makes Swift code accessible to Objective-C.

If you’re applying to an older company, they’ll likely have a substantial part of their codebase still in Objective-C. So, you’ll need to be comfortable with using both Objective-C and Swift in the same project.

This attribute is used to make your Swift code accessible to the Objective-C runtime.

Anytime you have a Swift class, property, or protocol that you want to access in Objective-C code, you’ll need to prefix it with this keyword.

// The ViewController is accessible in an Objective-C environment
@objc class ViewController: UIViewController {

// Visible in Objective-C
@objc var username: String!

// Not visible in Objective-C
private var password: String!

override func viewDidLoad() {
super.viewDidLoad()
}
}

@objc, when added to a class declaration, only exposes the public init. You will have to add it manually to all other properties and methods you want to expose.

If you want to expose all public properties and methods to Objective-C, you can use @objcMembers instead.

In Bullets
  • Purpose:

    • The @objc keyword is used to expose Swift code to the Objective-C runtime.
  • Usage:

    • Classes: Annotate Swift classes with @objc to make them accessible from Objective-C.
      @objc class MyClass: NSObject {
      // This class can be accessed from Objective-C code
      }
    • Methods: Use @objc for methods you want to be callable from Objective-C.
      @objc func myMethod() {
      // This method can be called from Objective-C
      }
    • Properties: Apply @objc to properties that need to be accessed in Objective-C.
      @objc var myProperty: String?
  • Limitations:

    • Private Members: Properties and methods marked private are not exposed to Objective-C even if they are annotated with @objc.
      @objc private var secret: String // Not visible in Objective-C
  • Objective-C Compatibility:

    • Enables interoperability between Swift and Objective-C, essential for projects with mixed-language codebases.

Do all elements in a tuple need to be the same type?

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

Main Source: 🔗 Ace the iOS Interview

Additional Sources:

Further Reading:

Tuples are a very convenient way to group elements together without having to create a custom object or struct to encapsulate them.

To create a tuple in Swift, simply specify a comma separated list of values within a set of parentheses like this:

let tuple = ( 1 , 2 , 3 , 123.0, "Hello, world!")
print(tuple. 4 ) // Hello, world!

As you can see, a tuple doesn’t have to be a homogenous set of types. It can easily be a mix of different types, but it’s up to you to keep track of what data type exists at each position and interact with it accordingly.

Does Swift support implicit casting between data types?

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

Main Source: 🔗 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

Swift does not support implicit casting.

Swift does not support implicit casting.

Take the following expression involving Doubles, Floats, and Integers:

let seconds: Double = 60
let minutes = 60 // minutes is inferred to be an Int
let hours: Float = 24.0
let daysInYear: Int = 365

// Fails with "Cannot convert value of type Float / Double to
// expected argument type Int"
let secondsInYear = seconds * minutes * hours * daysInYear

// You can see that we've had to explicitly cast all of the
// non-Integer types to Int
let secondsInYear = Int(seconds) * minutes * Int(hours) * daysInYear
print(secondsInYear) //31536000

When you have an expression with multiple types, Swift will not automatically convert them to a common shared type. Instead, Swift forces you to be explicit about how you want to deal with this mix of types.

Since the output can only be of one type, Swift leaves it up to the programmer to define what that should be.

How are Optionals implemented?

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

Optionals in Swift are implemented as enums with two cases: some, which holds an associated value, and none, which represents a nil value.

Optionals in Swift are a powerful feature that allow you to handle the absence of a value. Under the hood, Optionals are implemented as an enum with two cases: some, which contains an associated value, and none, which represents the absence of a value.

Here’s a simplified version of Swift’s Optional implementation:

public enum Optional<Wrapped>: ExpressibleByNilLiteral {
case none
case some(Wrapped)

public init(_ some: Wrapped) {
self = .some(some)
}

public init(nilLiteral: ()) {
self = .none
}
}

In the case of .some, there's an associated value, which is the non-nil case of an Optional variable. This enum-based structure leverages Swift’s features like generics, associated values, and value semantics, enabling developers to build expressive and powerful constructs.

Knowing that an Optional is simply an enum opens up possibilities for extending Optionals with custom behavior and convenience methods.

Additional Details

Understanding the implementation of Optionals allows developers to create custom extensions on Optionals, adding additional behavior and convenience methods. It also emphasizes the importance of value types and enums in building robust language features.

extension Optional {
func isSome() -> Bool {
if case .some = self {
return true
}
return false
}
}
In Bullets
  • Optionals as Enums: Swift Optionals are implemented as enums with two cases: some and none.
  • Associated Values: The some case holds an associated value, representing a non-nil value.
  • Custom Extensions: Understanding this allows for creating custom extensions to enhance Optional's functionality.

How can we limit a protocol conformance to a specific class or type?

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

Main Source: 🔗 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

Swift allows us to limit a protocol's conformance to a specific class or type by using the where keyword or by inheriting from a base class. This restriction can also be applied in protocol extensions to provide default implementations for specific types.

When designing protocols in Swift, there may be situations where you want to restrict the types that can conform to a protocol. For example, you might want only certain subclasses or types that conform to other protocols to adopt your custom protocol. This can be achieved in two primary ways:

  1. Inheriting from a Base Class: You can restrict protocol conformance by specifying a base class that the conforming types must inherit from.

    protocol TestViewController: UIViewController { }

    In this case, only subclasses of UIViewController can conform to the TestViewController protocol.

  2. Using the where Keyword: Another way to limit conformance is by using the where clause in your protocol declaration.

    protocol TestViewControllerAlternative where Self: UIViewController { }

    This is similar to inheriting from a base class but provides more flexibility and can be used in various contexts, such as with protocol extensions.

Example: Restricting Protocol Conformance in Extensions

You can also use these restrictions in protocol extensions to provide default implementations only for specific types.

protocol Animal {
func speak()
}

class Dog: Animal {}
class Cat: Animal {}

extension Animal where Self: Dog {
func speak() {
print("Woof!")
}
}

extension Animal where Self: Cat {
func speak() {
print("Meow!")
}
}

In the above example, the speak() method is only implemented for Dog and Cat types.

Limiting Conformance to Types that Conform to Another Protocol

If you want your protocol to only be adoptable by types that conform to another protocol, you can also enforce this using the where clause.

protocol SecureHashable: Hashable {
var secureHash: String { get }
}

Here, only types that already conform to Hashable can conform to SecureHashable.

Additional Details

Restricting protocol conformance is particularly useful in complex systems where you want to ensure certain behaviors are only available to specific types. This approach can help prevent misuse of protocols and provide a cleaner, more predictable API.

// Additional example: Restricting to classes that conform to another protocol
protocol LoggedInView where Self: UIViewController & UserSessionHandler {
func showLoggedInView()
}
In Bullets
  • Restricting with a Base Class: You can limit protocol conformance by specifying a base class, such as UIViewController.
  • Using where: The where clause adds flexibility, allowing for restrictions based on class or protocol conformance.
  • Selective Extensions: Apply protocol extensions conditionally to provide default implementations only for certain types.

How can you create a method with default values for its parameters?

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

Main Source: 🔗 Ace the iOS Interview

TL/DR

When we declare a function in Swift, we can specify defaults for our parameters by specifying values within the method declaration.

When we declare a function in Swift, we can specify defaults for our parameters by specifying values within the method declaration:

func sayHello(name: String = "reader")

Now, we can call this function directly without having to explicitly specify any parameters as the default values will be used instead.

If the function contains parameters that don’t have a default value specified, you’ll need to specify a value for that parameter as usual. Otherwise, the compiler will return a “missing argument” error.

Given this example function:

func sayHello(name: String = "reader") {
print("Hello, \(name)")
}

These two function calls are equivalent:

sayHello() 
sayHello(name: "reader")

Now, let’s consider this function declaration:

func logStatement(prettyPrint: Bool = false, includeTimestamp: Bool, enableVerboseMode: Bool = false, message: String) {}

As you can see, there are several parameters with default values specified, but includeTimestamp and message are explicitly required. When we create functions with a mix of parameters like this, Xcode’s auto-complete will help enumerate all of the valid variations of the call to our function.

In Bullets
  • Default Parameters: Swift allows you to specify default values for parameters within the function declaration.
  • Flexibility in Function Calls: If parameters have default values, they can be omitted when calling the function.
  • Required Parameters: Parameters without default values must be explicitly provided, otherwise, the compiler will throw an error.

How do we provide default implementations for protocol methods?

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

Main Source: 🔗 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

By leveraging Swift extensions, we can provide a default implementation for methods and properties declared in a protocol.

This helps reduce boilerplate and duplicated code in classes that implement the protocol while still allowing them to easily override the default implementation.

protocol Animal {
func makeNoise()
}

extension Animal {
func makeNoise() {
print("Bark!")
}
}

struct Dog: Animal {}
struct Cat: Animal {
func makeNoise() {
print("Meow!")
}
}

let sparky = Dog()
sparky.makeNoise() // Bark!

let whiskers = Cat()
whiskers.makeNoise() // Meow!

As you can see, we’ve provided the default implementation for makeNoise()in an extension.

Dog is using the default implementation while Cat is free to provide a more specific implementation.

This same approach allows us to make certain functions in our protocol optional. Since we have provided a default implementation in our extension, any entity that implements the protocolis no longer required to implement it.

protocol MyProtocol {
func doSomething()
}

extension MyProtocol {
func doSomething() {
/* Return a default value or just leave empty */
}
}

struct MyStruct: MyProtocol {
/* No compile error */
}

Alternatively, you can use @objc optional to make functions within your protocol optional. This would, however, restrict your protocol to only be implemented by class type objects, which would prevent your protocol from being used by structs, enums, etc. You’d also need to explicitly check if that optional method is implemented before you call it.

How do you use the Result type?

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

Main Source: 🔗 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

The Result type is a convenient way for us to handle both the success and failure cases of an operation while maintaining code readability.

Under the hood, the Result type, is an enum with two cases:

enum Result<Success, Failure> where Failure : Error {
/// A success, storing a `Success` value.
case success(Success)
/// A failure, storing a `Failure` value.
case failure(Failure)
}

The success case accepts any generic Success type(includingVoid) and failure takes the generic Failure type as its associated value.

Note: Whatever you use for the Failure’s associatedvalue must implement the Error protocol.

In the example below, you’ll see that we’re creating a PrimeNumberError enum with multiple cases. This approach allows us to have much greater specificity with our error messaging and has the added benefit of forcing us to handle each error case explicitly.

enum PrimeNumberError: Error {
case zero
case negative
case tooBig
}

func isPrimeNumber(num: Int) -> Result<Bool, PrimeNumberError> {
guard num < 0 else {
return .failure(.negative)
}

guard num > 0 else {
return .failure(.zero)
}

guard num < 1000 else {
return .failure(.tooBig)
}

return .success(primeNumberChecker(num: num))
}

switch isPrimeNumber(num: 23 ) {
case .success(let isPrime):
print("The number \(isPrime? "is" : "is not") prime")
case .failure(.tooBig):
print("The number is too big for this function.")
case .failure(.negative):
print("A prime number can't be negative.")
case .failure(.zero):
print("A prime number has to be greater than 1.")
}

Result is available in Swift 5.0+.

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)