Skip to main content

44 questions tagged with "Swift"

Swift tag description

View All Tags

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

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 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).

What are the differences between Swift’s access control modifiers?

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

Main Source: 🔗 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

Access controls allow us to restrict parts of our code from other source code files and modules.

This enables us to abstract away certain implementation details and instead share a preferred interface through which our code should be accessed and used.

Swift provides five different access level modifiers that can be used on classes, structs, enums, properties, methods, and initializers.

Going from the least to the most restrictive:

open: classes and methods marked asopencan be subclassedand overridden outside of their defining module.

public: provides read / write access to an entityto all other parts of the code regardless of the defining module.

internal: is the default access level in Swift. Propertiesdeclared asinternalare accessible in the scope of their defining module, but are inaccessible by parts of the code belonging to other modules.

fileprivate: entities marked asfileprivateare visibleand accessible from Swift code declared in the same source file.

private: is the most restrictive access level andensures that a property is only accessible inside their defining type. This means that properties and methods declared as private are only accessible within the class or structure in which they are defined.

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 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 copy on write mean?

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

Main Source: 🔗 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

We know that when we’re working with types that use pass-by-value semantics, we’re passing around a copy of the value instead of a direct memory reference.

Now, imagine that the value type we’re working with is storing a lot of data - for example an UIImage.

If we aren't going to modify this array, it's pointless to duplicate it every time we pass it to a function or assign it to a new variable as this would introduce a lot of unnecessary overhead.

That’s why Swift employs a resource management technique called “copy on write”. This technique allows us to efficiently implement a “copy” operation on a modifiable resource.

If the resource in question is never modified, then there’s no need to create a duplicate of it. Instead, we can provide shared access to this resource until such time an operation attempts to modify it. Then, and only then, will Swift duplicate the value.

This approach lets the system significantly reduce the resource consumption of pass-by-value operations in exchange for adding a small overhead to resource-modifying operations.

This functionality is available by default in arrays and dictionaries, but you’ll have to explicitly add it to any new value types you introduce.

What does defer do?

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

Main Source: 🔗 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

The defer keyword allows us to specify code that should be executed only when we are leaving the current function’s scope.

It's commonly used for releasing a shared resource, closing a connection, or performing any last-minute cleanup.

In this case, we're using it to close the connection to a database:

class DatabaseManager {
func writeLineToDatabase(entry: String) {
let database = Database()
defer {
database.disconnect()
}
database.connect()
do {
try database.write(entry: "Hello world!")
} catch {
print("An error occurred!")
}
}
}

You’ll see that regardless of whether the:

try database.write(entry: "Hello world!")

call succeeds or fails, we will always close out the database connection without having to duplicate the disconnect() call. Lastly, we can use defer to write setup and cleanup code next to each other, even though they need to be executed at different times.

What does it mean to be a first class function or type?

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

Main Source: 🔗 Ace the iOS Interview

Additional Sources:

Further Reading:

TL/DR

A first-class citizen is an entity that can be passed as an argument, returned from a function, modified, or assigned to a variable. So, a programming language is said to have first-class functions if it treats functions as first-class citizens.

In simple terms, this means the language supports passing functions as arguments to other functions, returning functions from functions, and assigning them to variables or storing them in data structures.

As we'll see in the following examples, Swift treats functions as first-class citizens.

Storing Functions In Variables We can easily create a function (or in this case a closure definition) and assign it to a variable:

class FirstClassCitizens {
var doubleInput:((Int) -> Int)?

func setup() {
doubleInput = { value in
return value * 2
}
}
}

Passing Functions As Arguments We can also pass functions as arguments to other functions.

For example, whenever we specify an animation in Swift, the second parameter is actually accepting a function.

class FirstClassCitizens {
func setup() {
UIView.animate(withDuration: 1.0) {
// This is a function passed as a parameter
}
UIView.animate(withDuration: 1.0, animations:{
// Same as above but without trailingclosure syntax
})
}
}

Returning Functions From Functions We can also return a function from a function as well:

class FirstClassCitizens {
func sayHello(name: String)-> (() -> String) {
return {
"Hello, \(name)"
}
}
}

let returnedFunction = FirstClassCitizens().sayHello(name: "Aryaman")
print(returnedFunction()) // Hello, Aryaman