Preparing for an iOS interview requires you to take considerable amount of time to review Swift language features. Try to skim as many following questions as possible, you might not remember every possible question but at least heard about it.
Swift encourages you to be clear about the types of values your code can work with. It performs type checks when compiling your code and flags any mismatched types as errors. This enables you to catch and fix errors as early as possible in the development process.
func foo(bar: String?) {
print(bar ?? "default value if missing")
}
Swift optionals let you indicate the absence of a value for any type at all, without the need for special constants. Either there is a value, and you can unwrap the optional to access that value, or there isn’t a value at all.
The concept of optionals doesn’t exist in Objective-C. The nearest thing in Objective-C is the ability to return nil
from a method that would otherwise return an object, with nil
meaning the absence of a valid object.
func foo(bar: Int) {
assert(bar > 1, "bar must be greater than 1")
/* ... */
}
Known from many programming languages, the assert()
is evaluated only in debug mode, will terminate application if condition not met. For release builds, lines with assert()
are ommited (condition is not evaluated).
func foo(bar: Int) {
precondition(bar > 1, "bar must be greater than 1")
/* ... */
}
The precondition()
ensures that the given condition is checked for both debug and release builds, will terminate the application if condition not met. For unchecked optimization level (-Ounchecked
) optimizer will assume that condition is always met.
Immutability helps safety and performance. Immutable objects are useful in multi-threaded applications because multiple threads can act on the data of immutable objects without worrying about changes to the data by other threads.
As immutable objects are closed to change, it is safe to assume that they will stay unchanged while we access the object from different threads. This assumption simplifies most of the multithreading problems that are complex to solve and maintain. For instance, we do not need to think about synchronization/locking mechanisms at all.
let items = ["foo", "bar"]
for (var i = 0; i < items.count; i += 1) {
print(items[i])
}
The C-style for-loop
appears to be a mechanical carry-over from C rather than a genuinely Swift-specific construct. It is rarely used and not very Swift-like.
More Swift-typical construction is already available with for-in
statements and stride. Removing for loops would simplify the language and starve the most common use-points for --
and ++
, which are already due to be eliminated from the language.
let items = ["foo", "bar"]
for item in items {
print(item)
}
for i in 0..<items.count {
print(items[i])
}
for (index, item) in items.enumerate() {
print("\(index): \(item)")
}
Closures are self-contained blocks of functionality that can be passed around and used in your code. Closures in Swift are similar to blocks in C and Objective-C and to lambdas in other programming languages.
Closures can capture and store references to any constants and variables from the context in which they’re defined. Closures take one of three forms:
Structures and classes are general-purpose, flexible constructs that have many things in common.
Classes have additional capabilities that structures don’t have:
The most basic distinguishing feature of a value type is that copying — the effect of assignment, initialization, and argument passing — creates an independent instance with its own unique copy of its data.
Copying a reference, on the other hand, implicitly creates a shared instance. After a copy, two variables then refer to a single instance of the data, so modifying data in the second variable also affects the original.
One of the primary reasons to choose value types over reference types is the ability to more easily reason about your code. Importantly, you can safely pass copies of values across threads without synchronization.
When you’re working with Cocoa, many APIs expect subclasses of NSObject
, so you have to use a class. For the other cases, here are some guidelines:
Use a value type when:
==
makes senseUse a reference type (e.g. use a class
) when:
===
makes sense@objc
vs dynamic
?class Foo {
func bar() -> Int {}
@objc func bar1() -> Int { /* ... */ }
@objc dynamic func bar2() -> Int { /* ... */ }
}
A function/variable declared as @objc
is accessible from Objective-C, but Swift will continue to access it directly via static or virtual dispatch. This means if the function/variable is swizzled via the Objective-C framework, like what happens when using Key-Value Observing or the various Objective-C APIs to modify classes, calling the method from Swift and Objective-C will produce different results.
Using dynamic
tells Swift to always refer to Objective-C dynamic dispatch. When the Swift function is called, it refers to the Objective-C runtime to dynamically dispatch the call. You might need this for KVO support or if you‘re doing method swizzling. The only way to do dynamic dispatch currently is through the Objective-C runtime, so you must add @objc
if you use dynamic
.
@escaping
in Swift?func foo(_ completion: @escaping () -> Void) {
DispatchQueue.main.async {
completion()
}
}
@escaping
is used to inform callers of a function that takes a closure that the closure might be stored or otherwise outlive the scope of the receiving function. This means that the caller must take precautions against retain cycles and memory leaks. It also tells the Swift compiler that this is intentional.
@objc
and @nonobjc
?When you use the @objc(name)
attribute on a Swift class, the class is made available in Objective-C without any namespacing. As a result, this attribute can also be useful when migrating an archivable Objective-C class to Swift. Because archived objects store the name of their class in the archive, you should use the @objc(name)
attribute to specify the same name as your Objective-C class so that older archives can be unarchived by your new Swift class.
Conversely, Swift also provides the @nonobjc
attribute, which makes a Swift declaration unavailable in Objective-C. You can use it to resolve circularity for bridging methods and to allow overloading of methods for classes imported by Objective-C. If an Objective-C method is overridden by a Swift method that cannot be represented in Objective-C, such as by specifying a parameter to be a variable, that method must be marked @nonobjc
.
The static type of a value is the known, compile-time type of the value. The dynamic type of a value is the value’s actual type at run-time, which can be a subtype of its concrete type.
You can use the type(of:)
function to find the dynamic type of a value, particularly when the dynamic type is different from the static type.
func printInfo(_ value: Any) {
let t = type(of: value)
print("'\(value)' of type '\(t)'")
}
let count: Int = 5
printInfo(count) // '5' of type 'Int'
The dynamic type returned from type(of:)
is a concrete metatype (T.Type) for a class, structure, enumeration, or other nonprotocol type T, or an existential metatype (P.Type) for a protocol or protocol composition P.